Timestamps are making my phpunit tests fail - laravel

When I use the following PostResource, and Post Test, my tests succeed:
PostResource.php
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'slug' => $this->slug,
];
}
PostController.php
public function show(Post $post)
{
return new PostResource($post);
}
ReadPostTest.php
/** #test */
public function a_user_can_read_a_single_post()
{
$post = factory(Post::class)->create([
'title' => 'A new post',
'content' => "Some content",
'slug' => "a-new-post",
]);
//dd($post);
$response = $this->json('GET', '/posts/' . $post->id)
->assertStatus(200)
->assertJsonFragment([ 'data' => [
'title' => 'A new post',
'content' => "Some content",
'slug' => "a-new-post",
]]);
}
When I add created_at and updated_at to my tests and resource I get a failure. The test bellow show when I didn't add .000000Z.
phpunit
There was 1 failure:
1) Tests\Feature\ReadPostsTest::a_user_can_read_a_single_post
Unable to find JSON:
[{
"data": {
"title": "A new post",
"content": "Some content",
"slug": "a-new-post",
"created_at": "2018-12-06 21:13:26",
"updated_at": "2018-12-06 21:13:26"
}
}]
within response JSON:
[{
"data": {
"id": 1,
"title": "A new post",
"content": "Some content",
"slug": "a-new-post",
"created_at": "2018-12-06T21:13:26.000000Z",
"updated_at": "2018-12-06T21:13:26.000000Z"
}
}].
Failed asserting that an array has the subset Array &0 (
'data' => Array &1 (
'title' => 'A new post'
'content' => 'Some content'
'slug' => 'a-new-post'
'created_at' => '2018-12-06 21:13:26'
'updated_at' => '2018-12-06 21:13:26'
)
).
--- Expected
+++ Actual
## ##
'title' => 'A new post',
'content' => 'Some content',
'slug' => 'a-new-post',
- 'created_at' => '2018-12-06 21:13:26',
- 'updated_at' => '2018-12-06 21:13:26',
+ 'created_at' => '2018-12-06T21:13:26.000000Z',
+ 'updated_at' => '2018-12-06T21:13:26.000000Z',
),
)
I tried adding 000000Z that and got the same problems.
There was 1 failure:
1) Tests\Feature\ReadPostsTest::a_user_can_read_a_single_post
Unable to find JSON fragment:
[{"data":{"content":"Some content","created_at":"2019-12-20 21:42:33.000000Z","id":1,"slug":"a-new-post","title":"A new post","updated_at":"2019-12-20 21:42:33.000000Z"}}]
within
[{"data":{"content":"Some content","created_at":"2019-12-20T21:42:33.000000Z","id":1,"slug":"a-new-post","title":"A new post","updated_at":"2019-12-20T21:42:33.000000Z"}}].
Failed asserting that false is true.
It seems like my created_at and up_dated at timestamps are messed up for a reason that I have no idea why? 2019-12-20T21:42:33.000000ZThat's probably what's getting my tests to fail. How do I fix this?

Use carbon on your post model factory and then use $post->created_at and $post->updated_at in the json assertion:
public function a_user_can_read_a_single_post()
{
$post = factory(Post::class)->create([
'title' => 'A new post',
'content' => "Some content",
'slug' => "a-new-post",
"updated_at" => Carbon::now()->timestamp,
"created_at" => Carbon::now()->timestamp
]);
//dd($post);
$response = $this->json('GET', '/posts/' . $post->id)
->assertStatus(200)
->assertJsonFragment([ 'data' => [
'id' => 1,
'title' => 'A new post',
'content' => "Some content",
'slug' => "a-new-post",
"updated_at" => $post->updated_at,
"created_at" => $post->created_at
]]);
}
For some reason a string of timestamps doesn't work. Hopefully someone else can comment as to why.

You probably want to use Carbon's setTestNow() to fix what 'now' means during the test.
Written with the assumption you are using Laravel.
// Immutable, so you don't go insane.
$now = \Carbon\CarbonImmutable::now()->micro(0);
// Fix Carbon's 'now' to that time
\Illuminate\Support\Carbon::setTestNow($now)
// \Carbon\CarbonImmutable::setTestNow($now);
// Your tests here
// u = microseconds, but it's not set to '000000' in 'now', if you forget `->micro(0)`.
// The 'Y-m-d\TH:i:s.u\Z' format works with Laravel 6.20.19 in march 2021, but YMMV.
// ->assertJSon(['created_at' => $now->utc()->format('Y-m-d\TH:i:s.000000\Z'),])
// Release Carbon's 'now' time to follow your clock again.
\Illuminate\Support\Carbon::setTestNow(null)
// \Carbon\CarbonImmutable::setTestNow(null);

It looks like you're struggling with the timestamps format. I had the same issue and solved it through the JSON serialization, found in the Carbon documentation. In your case, it must look like this:
public function a_user_can_read_a_single_post()
{
$post = factory(Post::class)->create([
'title' => 'A new post',
'content' => "Some content",
'slug' => "a-new-post",
"updated_at" => Carbon::now()->timestamp,
"created_at" => Carbon::now()->timestamp
]);
$response = $this->json('GET', '/posts/' . $post->id)
->assertStatus(200)
->assertJsonFragment([ 'data' => [
'id' => 1,
'title' => 'A new post',
'content' => "Some content",
'slug' => "a-new-post",
"updated_at" => $post->updated_at->jsonSerialize(),
"created_at" => $post->created_at->jsonSerialize()
]]);
}
It may be a little late, but hopefully helps to others.
Good luck buddy.

It works for me when I do this (Note: I put the date with ones to simplify the process):
use Carbon\Carbon;
class ExampleTest extends TestCase
{
protected function setUp(): void
{
Carbon::setTestNow(Carbon::create(1111, 1, 11, 11));
}
/** #test */
public function your_awesome_test()
{
$this->postJson(route('route.name'),[
'title' => 'A new post',
'content' => "Some content",
'slug' => "a-new-post",
])->assertJsonFragment([
'data' => [
'id' => 1,
'title' => 'A new post',
'content' => "Some content",
'slug' => "a-new-post",
"updated_at" => Carbon::setTestNow(),
"created_at" => Carbon::setTestNow()
]
];
}
}

Related

Laravel Phpunit testing a request that take give output based on the request

I'm still new to laravel and I have a simple app and aSo I have a route that will store data based on the request in my controller.
public funtion store(Request $request, $id){
if ($request->has('work_experiences')) {
WorkExperience::create([
'user_id' => $user->id,
'position' => $request->work_experiences['position'],
'company' => $request->work_experiences['company'],
'start_date' => $request->work_experiences['start_date'],
'end_date' => $request->work_experiences['end_date'],
]);
}
if ($request->has('education')) {
Education::create([
'user_id' => $user->id,
'degree' => $request->education['degree'],
'university' => $request->education['university'],
'start_date' => $request->education['start_date'],
'end_date' => $request->education['end_date'],
]);
}
if ($request->has('job_interests')) {
JobInterest::create([
'user_id' => $user->id,
'job_position' => $request->job_interests['position'],
]);
}}
}
and in my test
public function test_authenticated_user_can_edit_education_profile()
{
$this->withoutExceptionHandling();
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post('/candidate' . '/' . $user->id, [
'user_id' => $user->id,
'position' => 'position',
'company' => 'company',
'start_date' => Carbon::now(),
'end_date' => Carbon::now(),
]);
$this->assertCount(1, WorkExperience::all());
}
when I run the test, the assertCount seems to fail because the response didn't work/insert the data to DB. where do I do wrong?
Well, the test is right.
It should fail because there is no work_experiences key in your request data.
The test request should look like:
$response = $this->post('/candidate' . '/' . $user->id, [
'work_experiences' => [
'user_id' => $user->id,
'position' => 'position',
'company' => 'company',
'start_date' => Carbon::now(),
'end_date' => Carbon::now(),
]
]);
So your data should go under a work_experiences key such that $request->has('work_experiences') returns true and executes the WorkExperience::create() statement.
Currently your endpoint only allows for a single "work experience" to be created. Seeing that you've named it work_experiences I assume you'd want to pass in an array/collection of "work experiences" - but that won't work with the current implementation; you'll have to loop over them instead - something like this:
if ($request->has('work_experiences')) {
foreach ($request->input('work_experiences') as $experience) {
WorkExperience::create([
'user_id' => $request->user()->id,
'position' => $experience['position'],
'company' => $experience['company'],
'start_date' => $experience['start_date'],
'end_date' => $experience['end_date'],
]);
}
}
And then your test should look something like this:
$response = $this->post('/candidate' . '/' . $user->id, [
'work_experiences' => [
[
'user_id' => $user->id,
'position' => 'position',
'company' => 'company',
'start_date' => Carbon::now(),
'end_date' => Carbon::now(),
],
// more "work experiences"
]
]);

Laravel: Add custom data to resource

First I get the translator by his id using this line of code
$translator = Translator::where('id', $translator_id)->first();
Then I send a notification to him by this code:
$response = Http::withHeaders([
'Authorization' => 'key=myKey',
'Content-Type' => 'application/json'
])->post('https://fcm.googleapis.com/fcm/send', [
"notification" => [
"title" => "title",
"body" => "body",
],
"data" => [
"title" => "title",
"body" => "body",
],
"to" => $token,
]);
Everything works fine but my problem is that when I return the TranslatorResource I want to add the notification response to it, so I do this in my controller
$resource = new TranslatorResource($translator);
$resource->notif = $response;
return $resource;
And in TranslatorResource I have this code:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'phone' => $this->phone,
'cv' => $this->cv,
'specialization' => $this->specialization,
'tr_languages' => $this->tr_languages,
'all_languages' => $this->all_languages,
'isVerified' => $this->isVerified == 0 ? false : true,
'isActive' => $this->isActive == 0 ? false : true,
'completed_orders' => $this->completed_orders,
'canceled_orders' => $this->canceled_orders,
'rejected_orders' => $this->rejected_orders,
'current_orders' => $this->current_orders,
'isTranslator' => true,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
But I only get the data specified in the resource, the notif key isn't added, anyone know how to add this data to my resource when I return it ?
You can use additional method provided by laravel.
return (new TranslatorResource($translator))->additional(['notif ' => $response]);
Reference: Eloquent: API Resources
You can look for the section Adding Meta Data When Constructing Resources.

toArray is switching the "Create_at" and "Update_at" of a model when using "getCreatedAtAttribute" and "getUpdatedAtAttribute"

I have the following model:
abstract class ApiObjectModel extends Model
{
protected $appends = ['unique', 'created_by', 'updated_by'];
protected $hidden = ['deleted_at', 'head', 'create_id', 'update_id', 'createdByUser', 'updatedByUser'];
protected $casts = [
'created_at' => 'datetime:Y-m-d h:m',
'updated_at' => 'datetime:Y-m-d h:m',
];
/*
* other stuff
*/
public function getCreatedAtAttribute($value)
{
return Carbon::parse($value)->diffForHumans();
}
public function getUpdatedAtAttribute($value)
{
return Carbon::parse($value)->diffForHumans();
}
}
and this class:
class Ticket extends ApiObjectModel
{
protected $table = 'Ticket';
protected $fillable = ['Title', 'Description'];
}
I am running the following Test:
/** #test **/
public function updating_an_object_changes_the_update_at_time()
{
$response = $this->post('api/ticket', $this->basic_data());
//prüft Json
$response->assertJsonFragment([
'created_at' => $this->createtime,
'updated_at' => $this->createtime,
'created_by' => User::first()->name,
'updated_by' => User::first()->name,
]);
Carbon::setTestNow(Carbon::getTestNow()->addMinutes(30));
$response = $this->put('api/ticket/1', [
'Title' => 'Update'
]);
$response->assertJsonFragment([
'created_at' => $this->createtime,
'updated_at' => Carbon::getTestNow()->addMinutes(30)->diffForHumans(),
'created_by' => User::first()->name,
'updated_by' => User::first()->name,
]);
}
The Test fails because the created_at and the updated_at values are switched.
In my Controller, after doing some debug work, I found out the following result basically one line after the other:
dump(Ticket::findOrFail($id))
Result:
...
#attributes: array:8 [
"id" => 1
"Title" => "Update"
"Description" => "Change Description"
"create_id" => 1
"update_id" => 1
"created_at" => "2030-02-10 12:00:00"
"updated_at" => "2030-02-10 12:30:00"
"deleted_at" => null
]
...
dd(Ticket::findOrFail($id)->toArray())
Result:
array:8 [
"id" => 1
"Title" => "Update"
"Description" => "Change Description"
"created_at" => "30 minutes ago"
"updated_at" => "1 second ago"
"unique" => 1
"created_by" => "Test User 1"
"updated_by" => "Test User 1"
]
Can somebody help me understand and debug this?
(Laravel Framework 6.1.0)
nevermind... I was confusing the difftohuman() values from Carbon and set up the tests wrong.
This just passed:
/** #test **/
public function updating_an_object_changes_the_update_at_time()
{
$response = $this->post('api/ticket', $this->basic_data());
//prüft Json
$response->assertJsonFragment([
'created_at' => $this->createtime,
'updated_at' => $this->createtime,
'created_by' => User::first()->name,
'updated_by' => User::first()->name,
]);
Carbon::setTestNow(Carbon::getTestNow()->addMinutes(30));
$response = $this->put('api/ticket/1', [
'Title' => 'Update'
]);
$response->assertJsonFragment([
// following two lines got changed:
'created_at' => Carbon::now()->subMinutes(30)->diffForHumans(),
'updated_at' => Carbon::now()->diffForHumans(),
'created_by' => User::first()->name,
'updated_by' => User::first()->name,
]);
}
If you have other ideas how to spend your thoughts in circles... let me know

How to insert all json file data in database?

I have JSON file and have data in it. I want to import all data in the database through DB seeders. I am getting an error Trying to get property name of non-object. I have multiple data how I can insert in the database?
public function run()
{
$json = File::get("public/kmz/WASASubdivisions.geojson");
$data = json_decode($json);
// dd($data);
foreach ($data as $obj){
Regions::create(array(
'name' => $obj[0]->Name,
'description' => $obj[0]->description,
'altitudeMode' => $obj[0]->altitudeMode,
'Town' => $obj[0]->Town,
'AC' => $obj[0]->AC,
'No_of_TW' => $obj[0]->No_of_TW,
'No' => $obj[0]->No,
'DC'=> $obj[0]->DC,
'HH_2017' => $obj[0]->HH_2017,
'FID' => $obj[0]->FID,
'Area_ha' => $obj[0]->Area_ha,
'Field_1' => $obj[0]->Field_1,
'Pop_Dens' => $obj[0]->Pop_Dens,
'Id' => $obj[0]->Id,
'Pop_2017' => $obj[0]->Pop_2017,
'Area_Sq'=> $obj[0]->Area_Sq,
));
}
}
Sample Json Format
31 => {#837
+"type": "Feature"
+"properties": {#838
+"Name": "Gujjar Pura"
+"description": null
+"altitudeMode": "clampToGround"
+"Town": "Shalimar Town"
+"AC": "31"
+"No_of_TW": "11"
+"No": "13"
+"DC": "38"
+"HH_2017": "30478"
+"FID": "31"
+"Area_ha": "648.327"
+"Field_1": "Gujjar Pura"
+"Pop_Dens": "54063.141167"
+"Id": "0"
+"Pop_2017": "196619"
+"Area_Sq": "3.63684"
}
+"geometry": {#839
+"type": "MultiPolygon"
+"coordinates": array:1 [
0 => array:1 [
0 => array:169 [ …169]
]
]
}
}
Let's support I have a model Post and I want to save additional data in JSON format in the posts table. In that case, we can use property $casts in Laravel. Which will cast our field value to whatever we gave.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $table='posts';
protected $fillable = ['user_id', 'title', 'short_description', 'description', 'status', 'json_data'];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'json_data' => 'array',
];
}
Now we want to save data something like this
$data = [
'user_id' => 1,
'title' => 'abc',
'short_description' => 'test',
'description' => 'test',
'status' => true,
'json_data' => [
'additional_info' => '',
'post_image' => '',
...
],
];
$item = new Post;
$item->fill($data)->save();
This will save your json_data array values to JSON in the database. But when you will get data from the database it will convert that to array automatically.
For reference read this
since i am not a big fan of processing the json as a object
So the json_decode will accept the second arg so
$json = File::get("public/kmz/WASASubdivisions.geojson");
$data = json_decode($json,true);
dd($data);
foreach ($data as $obj)
{
Regions::create(array(
'name' => $obj['Name'],
'description' => $obj['description'],
'altitudeMode' => $obj['altitudeMode'],
'Town' => $obj['Town'],
'AC' => $obj['AC'],
'No_of_TW' => $obj['No_of_TW'],
'No' => $obj['No'],
'DC'=> $obj['DC'],
'HH_2017' => $obj['HH_2017'],
'FID' => $obj['FID'],
'Area_ha' => $obj['Area_ha'],
'Field_1' => $obj['Field_1'],
'Pop_Dens' => $obj['Pop_Dens'],
'Id' => $obj['Id'],
'Pop_2017' => $obj['Pop_2017'],
'Area_Sq'=> $obj['Area_Sq'],
));
}
Can You Post the dd() result and fileds sets of the table
public function run()
{
$json = File::get("public/kmz/WASASubdivisions.geojson");
$data = json_decode($json);
dd($data);
foreach ($data as $obj){
Regions::create(array(
'name' => $obj->Name,
'description' => $obj->description,
'altitudeMode' => $obj->altitudeMode,
'Town' => $obj->Town,
'AC' => $obj->AC,
'No_of_TW' => $obj->No_of_TW,
'No' => $obj->No,
'DC'=> $obj->DC,
'HH_2017' => $obj->HH_2017,
'FID' => $obj->FID,
'Area_ha' => $obj->Area_ha,
'Field_1' => $obj->Field_1,
'Pop_Dens' => $obj->Pop_Dens,
'Id' => $obj->Id,
'Pop_2017' => $obj->Pop_2017,
'Area_Sq'=> $obj->Area_Sq,
));
}
}
The attributes are under the properties key, but you're referencing them from the root of the object. e.g. $obj[0]->Name should be $obj[0]->properties->Name, etc.

mapWithKeys in laravel ,i dont understand how do it work?

I saw the example of laravel, but i dont understand how do it work.
for this example:
$collection = collect([
[
'name' => 'John',
'department' => 'Sales',
'email' => 'john#example.com'
],
[
'name' => 'Jane',
'department' => 'Marketing',
'email' => 'jane#example.com'
]
]);
$keyed = $collection->mapWithKeys(function ($item) {
return [$item['email'] => $item['name']];
});
$keyed->all();
someone can explain detail of it?
$collection = collect([
[
'name' => 'John',
'department' => 'Sales',
'email' => 'john#example.com'
],
[
'name' => 'Jane',
'department' => 'Marketing',
'email' => 'jane#example.com'
]
]);
$keyed = $collection->mapWithKeys(function ($item) {
//this line takes one array of collection object in item array and make a key of its email and store name on that email key
return [$item['email'] => $item['name']];
});
$keyed->all();

Resources