i'm learning about unit testing with CodeIgniter and i would to ask some questions about testing queries with Mocks class.
I tried to implement the following class News_model with the method get_news_all() that returns all news data from table 'news' and get_news returns only title and text fields from the same.
class News_model extends CI_Model
{
public function __construct(){
$this->load->database();
}
public function get_news_all()
{
$query=$this->db->get('news');
$result=$query->result_array();
return $result;
}
public function get_news()
{
$this->db->select('title, text');
$this->db->from('news');
$query=$this->db->get();
$result=$query->result_array();
return $result;
}
After i tried to build a News_model_with_mocks_test for testing method get_news_all() and in this case test runs fine:
class News_model_with_mocks_test extends TestCase
{
public function setUp()
{
$this->resetInstance();
$loader=$this->getMockBuilder('CI_Loader')->setMethods(['database'])->getMock();
$loader->method('database')->willReturn($loader);
$this->CI->load=$loader;
if(!class_exists('CI_DB', false))
{
eval('class CI_DB extends CI_DB_query_builder {}');
}
$this->obj=new News_model();
}
public function test_1()
{
$result_array = [
[
"id" => "1",
"title" => "News",
"slug" => "news",
"text" => "News",
],
[
"id" => "2",
"title" => "News2",
"slug" => "news2",
"text" => "News2",
],
];
$db_result=$this->getMockBuilder('CI_DB_result')->disableOriginalConstructor()->getMock();
$db_result->method('result_array')->willReturn($result_array);
$db = $this->getMockBuilder('CI_DB')->disableOriginalConstructor()->getMock();
$db->expects($this->once())->method('get')->with('news')->willReturn($db_result);
$this->obj->db=$db;
$result=$this->obj->get_news_all();
$this->assertEquals($result_array,$result);
}
}
But i don't know how to do tests for the method get_news(), i tried something as this:
public function test_1()
{
$result_array2 = [
[
"title" => "News",
"text" => "News",
],
[
"title" => "News2",
"text" => "News2",
],
];
$db_result=$this->getMockBuilder('CI_DB_result')->disableOriginalConstructor()->getMock();
$db_result->method('result_array')->willReturn($result_array2);
$db = $this->getMockBuilder('CI_DB')->disableOriginalConstructor()->getMock();
$db->expects($this->once())->method('query')->with('select title,text from news')->willReturn($db_result);
$this->obj->db=$db;
$result=$this->obj->get_news();
$this->assertEquals($result_array2,$result);
}
phpunit thows the following exception:
PHP Fatal error: Call to a member function result_array() on a non- object in /opt/lampp/htdocs/codeigniter/application/models/Users_model.php on line 21
I don't know how to test double with select queries! Thank you in advance for your answers.
i read more documentation in these days and i understood that it was a misunderstanding for me about usage of Mocks. In other words we have to define methods and returning values that we will expect and inject them to original classes. This is the method get_users of the class News_model that i wrote above:
public function get_news()
{
$this->db->select('title, text');
$this->db->from('news');
$query=$this->db->get();
$result=$query->result_array();
return $result;
}
We simply want that the get() method will return a result array which contains only title and text fields for each record:
class News_model_with_mocks_test extends TestCase
{
public function setUp()
{
$this->resetInstance();
$loader=$this->getMockBuilder('CI_Loader')->setMethods(['database'])->getMock();
$loader->method('database')->willReturn($loader);
$this->CI->load=$loader;
if(!class_exists('CI_DB', false))
{
eval('class CI_DB extends CI_DB_query_builder {}');
}
$this->obj=new News_model();
}
public function test_1()
{
$result_array = [
[
"title" => "News test",
"text" => "News text",
],
[
"title" => "News2",
"text" => "Testo news2",
],
];
$db_result=$this->getMockBuilder('CI_DB_result')->disableOriginalConstructor()->getMock();
$db_result->method('result_array')->willReturn($result_array);
$db = $this->getMockBuilder('CI_DB')->disableOriginalConstructor()->getMock();
$db->expects($this->once())->method('get')->willReturn($db_result);
$this->obj->db=$db;
$result=$this->obj->get_news();
$this->assertEquals($result_array,$result);
}
}
I hope that this solution could help someone that could have same doubts!
Related
I was required to return this json, I am using api resources in Laravel:
{
"data": [
{
"type": "users",
"id": "1",
"attributes": {
"name": "test name",
"lastname": "test lastname"
"projects": 2
},
"relationships": {
"projects": {
"data": [
{
"id": 1,
"type": "projects"
}
]
}
}
}
],
"included": [
{
"type": "projects",
"id": 1,
"attributes": {
"title" : "Test",
"description": "Test",
....
....
}
}
]
}
A user has many projects, I am doing this:
ProjectCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ProjectCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => $this->collection
];
}
}
ProjectResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ProjectResource extends JsonResource
{
public function toArray($request)
{
return [
"type" => "projects",
"id" => $this->id,
"attributes" => [
"title" => $this->title,
"description" => $this->description,
....
....
]
];
}
}
UserCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => $this->collection
];
}
public function with($request)
{
return [
//'included' => ProjectResource::collection($this->collection->map->only(['firstProject']) it doesn't work
'included' => new ProjectCollection($this->collection->map->only(['firstProject']) // it doesn't work
];
}
}
UserResource.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
public function toArray($request)
{
return [
"type" => "users",
"id" => $this->id,
"attributes" => [
"name" => $this->name,
"lastname" => $this->lastname
"projects" => $this->whenCounted('projects')
],
"relationships" => [
"projects" => new ProjectCollection($this->firstProject),
]
];
}
}
Models/User.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class User extends Model
{
use HasFactory;
protected $guarded = ['id'];
public function projects()
{
return $this->hasMany(Project::class);
}
public function firstProject()
{
return $this->projects()->oldest()->limit(1);
}
}
UsersController.php
$users = User::withCount('projects')->latest()->get();
return new UserCollection($users);
I am getting this error:
ErrorException PHP 8.1.1
9.39.0 Attempt to read property "id" on array
What can I do? Thank you.
I was able to solve this issue this way:
UserCollection.php
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class UserCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => $this->collection
];
}
public function with($request)
{
$collection1 = $this->collection->map->only(['firstProject']);
$collection2 = $collection1->map(function($item, $key) {
return $item['firstProject'];
});
return [
'included' => $collection2
];
}
}
Now I have another issue missing, but I will post another question, thanks
I am trying to build a resource collection with a second resource based on a hasManyThrough relationship. When i call the actual relationship on the main resource it does provide me the correct data. Whenever i pass the data to another resource and use the whenLoaded() method, it doesn't chain the data to the main collection.
The model:
public function fields()
{
return $this->hasManyThrough(Field::class, FieldValue::class, 'component_id', 'id', 'id', 'field_id');
}
The controller:
/**
* #return JsonResponse
*/
public function index()
{
$components = Component::with(['fields'])->get();
return ComponentValueResource::collection($components)->response();
}
ComponentValueResource class:
public function toArray($request)
{
return [
'fields' => FieldResource::collection($this->whenLoaded('fields')),
];
}
FieldResource class:
public function toArray($request)
{
$fields = $this->whenLoaded('fields');
return [
'fields' => new ComponentValueResource($fields),
];
}
Output of dd(FieldResource::collection($this->whenLoaded('fields')));
#original: array:9 [
"id" => 1
"base_id" => 1
"component_id" => null
"identifier" => "text input"
"type" => "text"
"position" => 1
"created_at" => "2021-08-22 19:44:23"
"updated_at" => "2021-08-22 19:44:23"
"laravel_through_key" => 6
]
Output from the resource:
{
"data": [
{
"id": 6,
"fields": [
[]
]
},
I'm trying to create an API for my data tables using Laravel's resource. I have three models with relationships. Every time I hit my api routes to check the result I'm getting a null value in my sub_specializations. Here's the result already JSON formatted.
{
"data":[
{
"first_name":"Rusty",
"last_name":"Ferry",
"specializations":{
"specialization_id":11,
"specialization_name":"Endocrinology"
},
"sub_specializations":null
},
{
"first_name":"Nadia",
"last_name":"Ondricka",
"specializations":{
"specialization_id":22,
"specialization_name":"ENT"
},
"sub_specializations":null
},
{
"first_name":"Erich",
"last_name":"Torphy",
"specializations":{
"specialization_id":2,
"specialization_name":"Cardiologist"
},
"sub_specializations":null
}
]
}
Here are all my resources. This the DoctorsResource
public function toArray($request)
{
return [
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'specializations' => new SpecializationsResource($this->specializations),
'sub_specializations' => new SubSpecializationsResource($this->sub_specializations),
];
}
Specializations Resource
public function toArray($request)
{
return [
'specialization_id' => $this->specialization_id,
'specialization_name' => $this->specialization_name,
];
}
SubSpecializations
public function toArray($request)
{
return [
'sub_specialization_id' => $this->sub_specialization_id,
'sub_specialization_name' => $this->sub_specialization_name,
'doctors' => new DoctorsResource($this->doctors),
];
}
Lastly, this is the controller
protected $user;
public function __construct(Doctors $doctors){
$this->doctors = $doctors;
}
public function index()
{
$doctors = $this->doctors->with('specializations', 'subSpecializations')->get();
return DoctorsResource::collection($doctors);
}
The result that I'm expecting is similar to this
{
"data":[
{
"first_name":"Rusty",
"last_name":"Ferry",
"specializations":{
"specialization_id":11,
"specialization_name":"Endocrinology"
},
"sub_specializations": {
"sub_specialization_name":"value"
}
}
]
}
You have to make sure there is data of Sub Specializations for particular doctor.
If there is data then add that data to Doctor Resource otherwise it will be blank.
Just need to change line in doctor Resource like:
'sub_specializations' => $this->sub_specializations !== null ? new SubSpecializationsResource($this->sub_specializations) : '',
You can do same thing with specializations also.
My Post Model has the following format:
{
"id": 1,
"title": "Post Title",
"type: "sample"
}
Here is my controller method:
public function show($id) {
$post = App\Post::find($id);
$transformedPost = new PostResource($post);
return $transformedPost;
}
Here is how my PostResource looks:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->title,
'type' => $this->convertType($this->type),
];
}
public function convertType($type)
{
return ucfirst($type);
}
So in show/1 response, I should get:
{
"id": 1,
"name": "Post Title",
"type: "Sample"
}
Instead, I am getting:
{
"id": 1,
"title": "Post Title",
"type: "sample"
}
So my PostResource is clearly not working as expected. Key "title" is not being substituted by key "name".
What am I missing here? I know there could be possible duplication of this post but the solutions in other questions seem not working for me.
I am using Laravel 6.x.
//I'm trusting you want to use an Accessor.
//In your Post Model, try something like this
public function getTypeAttribute($value)
{
return ucfirst($value);
}
Your PostResource should now be
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->title,
'type' => $this->type
];
}
Short way;
PostResource;
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->title,
'type' => ucfirst($this->type)
];
}
Please, sorry for my English.
My problem:
abstract class Entity
{
protected static $fieldNames;
public static function getFieldsNames()
{
if (is_null(static::$fieldNames)) {
foreach (static::$fieldsMap as $name => $map) {
static::$fieldNames[] = $name;
}
}
return static::$fieldNames;
}
}
class User extends Entity
{
protected static $fieldsMap = [
'id' => [
// ...
],
'name' => [
// ...
],
'phone' => [
// ...
]
];
}
class Car extends Entity
{
protected static $fieldsMap = [
'id' => [
// ...
],
'brand' => [
// ...
],
'color' => [
// ...
]
];
}
print_r(User::getFieldsNames());
// ['id', 'name', 'phone'] - On first call it works as expected, but...
print_r(Car::getFieldsNames());
// ['id', 'name', 'phone'] :(
If I declare $fieldNames in User and Car classes work fine, but in real project I has tens of static variables such $fieldNames and hundreds of entity's
Is it possible to best solution?
Maybe create small repository class that will keep these static variables by entity's id? or another elegant way?
Thanks any Help!
$fieldNames is static so it's associated with the class itself and not with a specific object.
The class in this instance is "Entity".
Once you set it it is no longer null.