trevorpan's avatar

Suggestions on test: a_logged_in_user_can_view_an_individual_job()

Wondering if this is getting all the important assertions? Basically, the concept is there are jobs on a "blog" type page or 3 paginated results. If a user clicks details then they are required to either login (that test passes) or they are allowed to see the results.

The deadline attribute requires some footwork (taking integer, as weeks, from html select), but otherwise the factory spins up a fake job. Maybe the whole procedure should happen in the factory?

The only other question I had was on the RefreshDatabase trait. I read in the docs you should use DatabaseMigrations does that affect this make() or is it more appropriate to use create().

<?php

namespace Tests\Feature;

use Mockery;
use Session;

use App\Job;
use App\User;
use Carbon\Carbon;
use Tests\TestCase;
use Illuminate\Http\Request;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Facades\Event;

class ViewPostedJobsTest extends TestCase
{
    use WithFaker, DatabaseMigrations;

    /** @test */
    public function a_logged_in_user_can_view_an_individual_job()
    {
        $this->withoutExceptionHandling();
        Event::fake();
        $user = (factory(User::class)->create());
        $basejob = (factory(Job::class)->make());

        $job = Carbon::now()
            ->addWeeks($basejob['deadline'])
            ->toDateTimeString();

        dd($job);  

        $response = $this->be($user)->get('/jobs/'.$job->id);

        $response->assertStatus(200);  
    }
}

Response from dd$job);

PHPUnit 7.5.13 by Sebastian Bergmann and contributors.

........"2019-09-03 23:58:13"

The conversion ends up stripping the job attributes.

<?php

use Faker\Generator as Faker;


$factory->define(App\Job::class, function (Faker $faker) {
    return [
            'jobtitle' => $this->faker->sentence,
            'body' => $this->faker->paragraph,
            'deadline' => $this->faker->numberBetween(1, 10),
            'user_id' => $this->faker->numberBetween(1, 1000),
            'projectaddress' => $this->faker->streetAddress,
            'city' => $this->faker->city,
            'state' => $this->faker->state,
            'zipcode' => $this->faker->postcode,
            'biddertype' => $this->faker->word,
            'job' => $this->faker->word,
            'subjob' => $this->faker->word
    ];
});
0 likes
9 replies
trevorpan's avatar

After looking at this, it seems like the deadline should really be handled in the factory.

Having a bit of a time with the closure...

<?php

use Carbon\Carbon;
use Faker\Generator as Faker;

$deadline = $this->faker->numberBetween(1, 10);

$factory->define(App\Job::class, function (Faker $faker, $deadline) {
    return [
            'jobtitle' => $this->faker->sentence,
            'body' => $this->faker->paragraph,
            // 'deadline' => $this->faker->numberBetween(1, 10),
            'deadline' => function () use ($deadline) {
                return Carbon::now()
                    ->addWeeks($deadline)
                    ->toDateTimeString();
            },
            'user_id' => $this->faker->numberBetween(1, 1000),
            'projectaddress' => $this->faker->streetAddress,
            'city' => $this->faker->city,
            'state' => $this->faker->state,
            'zipcode' => $this->faker->postcode,
            'biddertype' => $this->faker->word,
            'job' => $this->faker->word,
            'subjob' => $this->faker->word
    ];
});

The dd($job); in the test does not throw an error, but I can see the deadline does not addWeeks(). It seems to have added a day though!

  #attributes: array:11 [
    "jobtitle" => "Doloribus quisquam rerum quia rerum aut sint."
    "body" => "Ullam recusandae odit quisquam ut minus harum ex. Delectus voluptas atque aut et vel mollitia quibusdam."
    "deadline" => "2019-07-24 01:44:06"
    "user_id" => 948
    "projectaddress" => "5826 Antonietta Shores"
    "city" => "South Clementine"
    "state" => "Oklahoma"
    "zipcode" => "17962"
    "biddertype" => "est"
    "job" => "suscipit"
    "subjob" => "quia"
Talinon's avatar

@trevorpan

First of all, your job attributes are not getting stripped. You are assigning the temporary variable $job to the result of a string function toDateTimeString() of Carbon. If you dd($basejob) you'll see your attributes.

As for make vs create: make() generates a model within memory, where create() generates a model and stores it to the database. An example where make() comes in handy when you want to whip up a in-memory model to pass as parameters to a form post. Then you can assert that the model was persisted to the database via your application logic. As for your specific test above, you would want to use create(), otherwise when you test your get response your model won't exist in the database to be displayed on the page. If you were testing something like "a_job_can_be_persisted_to_the_database_upon_passing_validation" then using make() would be more appropriate.

UsingRefreshDatabase is likely fine, unless you have a specific reason not to use it. It will decide for you to run transactions or migrations, depeding on what database you're using.

Lastly - you might want to make some assertSee() calls to assert that the model is visible to the user:

$response->assertStatus(200)
    ->assertSee($basejob->jobtitle);  // for example
trevorpan's avatar

Hi @talinon

alright, thank you.

I dd($basejob); you are right!

  #original: array:14 [
    "jobtitle" => "Labore inventore consequatur iusto tempora sunt."
    "body" => "Quibusdam atque aliquid error minima quis. Rerum et laboriosam voluptatem magnam distinctio voluptatem. Possimus et facilis rem deserunt nihil nihil necessitatibus. Magnam laboriosam sed qui porro praesentium ut."
    "deadline" => 5
    "user_id" => 933
    "projectaddress" => "87635 Harvey Terrace Apt. 386"
    "city" => "Queenfurt"
    "state" => "New Hampshire"
    "zipcode" => "69084"
    "biddertype" => "incidunt"
    "job" => "sunt"
    "subjob" => "hic"
    "updated_at" => "2019-07-24 02:08:37"
    "created_at" => "2019-07-24 02:08:37"
    "id" => 1

Thisdeadlinepiece of code has been really tricky for me (multiple times trying to get it right on laracasts, now with tests it's trouble again); here the faker gives an integer but does not convert it to a dateTime() this what I was trying to do with $basejob variable. Is this a case for array_merge()?

. When i comment out dd($basejob); it errors out:

There was 1 error:

1) Tests\Feature\ViewPostedJobsTest::a_logged_in_user_can_view_an_individual_job
ErrorException: Trying to get property of non-object

/Users/trevorpan/code/bidbird/tests/Feature/ViewPostedJobsTest.php:74 
((the $response = $this->be($user)->get('/jobs/'.$job->id); line))

That's always a pain. I can see it's an array from the dd but am unclear if this should be an object or an array of objects?

Talinon's avatar

@trevorpan

A factory will return an object.

You're getting that error because you're trying to access an object property on something that isn't an object - I suspect you've still assigned something to the $job variable other than what was returned from the factory.

Here, let me see if I can put you on the right path.. something like this might work:

First, make deadline a datetime datatype in your migration.


   public function a_logged_in_user_can_view_an_individual_job()
    {
        $this->withoutExceptionHandling();
        Event::fake();
        $user = (factory(User::class)->create());
        $job = (factory(Job::class)->create(['deadline' => Carbon::now()->addWeeks(3)]);

        $response = $this->be($user)->get('/jobs/'.$job->id);

        $response->assertStatus(200)
        ->assertSee($job->jobtitle)
        ->assertSee($job->formattedDeadline);
    }

Job Model:

 public function getFormattedDeadlineAttribute()
    {
        return $this->deadline->toDateTimeString();  // or however you want the date to be formatted
    }

JobFactory:

use Carbon\Carbon;

$factory->define(App\Job::class, function (Faker $faker) {
    return [
            'jobtitle' => $this->faker->sentence,
            'body' => $this->faker->paragraph,
            'deadline' => Carbon::now()->addWeeks(rand(1,5)),  // or override as I did above in create()
            'user_id' => $this->faker->numberBetween(1, 1000),
            'projectaddress' => $this->faker->streetAddress,
            'city' => $this->faker->city,
            'state' => $this->faker->state,
            'zipcode' => $this->faker->postcode,
            'biddertype' => $this->faker->word,
            'job' => $this->faker->word,
            'subjob' => $this->faker->word
    ];
});

Then in your view, use {{ $job->formattedDeadline }}

trevorpan's avatar

ahh cool. I wonder if modifying the factory($job) in the test is what alters the object and throws that error?

I gave this line a shot in the test:

$job = (factory(Job::class)->create([
            'deadline' => Carbon::now()
            ->addWeeks(3)
        ]));

dd($job); gives the below:

  #original: array:14 [
   // "jobtitle" => "Voluptatem ut enim rem tenetur."
    //"body" => "Sit similique qui quidem illo est. A aliquid in est aut et quis."
    "deadline" => Carbon\Carbon @1565752597^ {#2471}
   // "user_id" => 683
    //"projectaddress" => "16725 Marty Pine"
    //"city" => "Blairberg"
   // "state" => "Idaho"
    //"zipcode" => "99226"
   // "biddertype" => "quia"
   // "job" => "ipsum"
   // "subjob" => "deleniti"
    //"updated_at" => "2019-07-24 03:16:37"
  //  "created_at" => "2019-07-24 03:16:37"
    //"id" => 1
  ]

After removing dd:

Time: 3.88 seconds, Memory: 30.00 MB

There was 1 failure:

1) Tests\Feature\ViewPostedJobsTest::a_logged_in_user_can_view_an_individual_job
Expected status code 200 but received 500.
Failed asserting that false is true.

/Users/trevorpan/code/bidbird/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:150
/Users/trevorpan/code/bidbird/tests/Feature/ViewPostedJobsTest.php:60

I also tried the without the carbon in the test, just the factory as you wrote - it has a similar 500 error.

Was looking further into factories https://laravel.com/docs/5.8/database-testing#writing-factories , and started sketching out a closure but it's all jacked up...

Do you thing the $factory->afterMaking(App\Job::class, function ($job) {... approach should include the deadline?

Again, super big thank you!

Talinon's avatar
Talinon
Best Answer
Level 51

@trevorpan

Check your logs to see the cause of the 500 error.

I didn't pay much attention to it yesterday, but another problem you'll run into (if its not already the source of your error) is how you're handling the user_id within your factory. It should reference an actual user's id.

Change it like so:

...
'user_id' => function() { return factory(App\User::class)->id },
...

That way, it'll create a user within your database and return back its id.

Again, you can always override it with the make() or create() methods when you're using the factory. For example, within your test, if you would like $user to be the owner of the job, you can do this:

 $job = (factory(Job::class)->create([
    'deadline' => Carbon::now()->addWeeks(3),
    'user_id' => $user->id
 ]);

trevorpan's avatar

@talinon

Mad genius!

My man, so happy got this thing to go. Modified your suggestion slightly to include the carbon() in the factory with afterMaking() which neatly takes the dateTime() stuff out of the test! The user->id bit was key.

Thank you. This is a mighty step forward.

  /** @test */
    public function a_logged_in_user_can_view_an_individual_job()
    {
        Event::fake();
        $user = (factory(User::class)->create());
        $job = (factory(Job::class)->create([
        'user_id' => $user->id
        ]));

        $response = $this->actingAs($user)->get('/jobs/'.$job->id);

        $response->assertStatus(200)
            ->assertSee($job->jobtitle)
            ->assertSee($job->deadline);      
    }
<?php

use Carbon\Carbon;
use Faker\Generator as Faker;

$factory->define(App\Job::class, function (Faker $faker) {
    return [
            'jobtitle' => $this->faker->sentence,
            'body' => $this->faker->paragraph,
            'deadline' => $this->faker->numberBetween(1, 10),
            // 'deadline' => Carbon::now()->addWeeks(rand(1,5)),
            'user_id' => $this->faker->numberBetween(1, 1000),
            'projectaddress' => $this->faker->streetAddress,
            'city' => $this->faker->city,
            'state' => $this->faker->state,
            'zipcode' => $this->faker->postcode,
            'biddertype' => $this->faker->word,
            'job' => $this->faker->word,
            'subjob' => $this->faker->word
        ];
    });


$factory->afterMaking(App\Job::class, function ($job) {
            return [ Carbon::now()
                    ->addWeeks($job['deadline'])
                    ->toDateTimeString()
                ];
});
Trevors-Mac-mini:bidbird trevorpan$ ./vendor/bin/phpunit
PHPUnit 7.5.13 by Sebastian Bergmann and contributors.

.........                                                           9 / 9 (100%)

Time: 1.09 seconds, Memory: 26.00 MB

OK (9 tests, 16 assertions)
Trevors-Mac-mini:bidbird trevorpan$ 
trevorpan's avatar

Made a refactor, after realizing while the test passed the deadline was not truly returning a dateTime(). (for anyone coming to this later)

Also the line return (factory(User::class)->id); gave an error: ErrorException: Undefined property: Illuminate\Database\Eloquent\FactoryBuilder::$id so I added ->create()->id which seemed to fix it.

However I was puzzled why user_id said 2. Well, I commented out the creation of the user in the test and bam! user_id = 1 when dd$job);

Feel like I'm finally getting somewhere.

$factory->define(App\Job::class, function (Faker $faker) {

    $deadline = $this->faker->numberBetween(1, 10);

    return [
            'jobtitle' => $this->faker->sentence,
            'body' => $this->faker->paragraph,
            'deadline' => function () use ($deadline) {
                return Carbon::now()
                    ->addWeeks($deadline)
                    ->toDateTimeString();
            },
            'user_id' => function() {
                return (factory(User::class)->create()->id);
            },
            'projectaddress' => $this->faker->streetAddress,
            'city' => $this->faker->city,
            'state' => $this->faker->state,
            'zipcode' => $this->faker->postcode,
            'biddertype' => $this->faker->word,
            'job' => $this->faker->word,
            'subjob' => $this->faker->word
        ];
    });
    /** @test */
    public function a_logged_in_user_can_view_an_individual_job()
    {
        // $this->withoutExceptionHandling();
        Event::fake();
        //$user = (factory(User::class)->create());
        $job = (factory(Job::class)->create());

        // dd($job);

        $response = $this->actingAs($user)->get('/jobs/'.$job->id);

        $response->assertStatus(200)
            ->assertSee($job->jobtitle)
            ->assertSee($job->deadline);   
    }
Talinon's avatar

@trevorpan

Glad you got it figured out.. sorry about the factory typo - I'm accustomed to using a helper function to make it a bit more shorthand.

1 like

Please or to participate in this conversation.