thebigk's avatar
Level 13

Why is assertDatabaseHas failing in this test case?

Here's my test case

public function an_authenticated_user_may_participate_in_forum() {
        $this->withoutExceptionHandling(); // Disable exception handling. Throw the exception at right place
        $user = factory('App\User')->create();
        $this->be($user);

        $thread = factory('App\Thread')->create(); // Thread ID 1
        $reply = factory('App\Reply')->make();

        $this->post($thread->path() . '/replies', $reply->toArray());

        $this->get($thread->path())
                ->assertSee($reply->body);
        $this->assertDatabaseHas('replies', $reply->toArray());
    }

The test works fine if I comment out the last line $this->assertDatabaseHas('replies', $reply->toArray());

Here's what phpUnit says:

Failed asserting that a row in the table [replies] matches the attributes {
    "user_id": 3,
    "thread_id": 2,
    "body": "Sint labore animi id hic blanditiis eius consequatur temporibus."
}.

Found: [
    {
        "id": "1",
        "user_id": "1",
        "thread_id": "1",
        "body": "Sint labore animi id hic blanditiis eius consequatur temporibus.",
        "created_at": "2017-10-27 13:03:35",
        "updated_at": "2017-10-27 13:03:35"
    }
].

It's apparent that a new thread is being created; but I can't figure out where and how. Would really appreciate your help.

0 likes
13 replies
tykus's avatar

Are you making sure that the authenticated user is leaving the reply inside your controller? It looks like your Reply factory also creates a thread?

thebigk's avatar
Level 13

Yes it does. Here's the RepliesController -

public function store(Thread $thread, Request $request) {

        Reply::create([
            'thread_id' =>  $thread->id,
            'user_id'   =>  auth()->id(),
            'body'  =>  $request->body,
        ]);

        return back();
    }

and the ReplyFactory

$factory->define(App\Reply::class, function (Faker $faker) {
    return [
        'user_id' => function() { return factory('App\User')->create()->id; },
        'thread_id' => function() { return factory('App\Thread')->create()->id; },
        'body'  =>  $faker->sentence
    ];
});
tykus's avatar
tykus
Best Answer
Level 104

Your $reply in the assertion is using the values that the factory whipped up for the user and thread it created. It does not know about the values that the request created.

Really the reply request will only have a body - I don't know why you would want to make a Role from the factory for this. Simulate the actual request payload, which will not have a user_id or thread_id

Hiding the content of the reply doesn't benefit you at all, be explicit about what you expect to see, e.g.

public function an_authenticated_user_may_participate_in_forum()
{
    $user = factory('App\User')->create();
    $this->be($user);

    $thread = factory('App\Thread')->create();

    $this->post($thread->path() . '/replies', [
        'body' => 'This is my awesome reply'
    ]);

    $this->get($thread->path())->assertSee($reply->body);
    $this->assertDatabaseHas('replies', [
        'thread_id' => $thread->id,
        'user_id' => $user->id,
        'body' => 'This is my awesome reply'
        
    ]);
    }
2 likes
thebigk's avatar
Level 13

Interesting, @tykus!

So I checked Jeffrey's code and he's created a reply as follows:

Reply::create([
            //'thread_id' =>  $thread->id,  <- He didn't use it.
            'user_id'   =>  auth()->id(),
            'body'  =>  $request->body,
        ]);

I'm wondering, how'd the thread_id be populated with this code? A reply needs to have a thread_id!

What am I missing here?

tykus's avatar

Are you sure about this? Didn't he have an addReply() method on the Thread model? Are you sure it is Reply::create([...]) and not $thread->addReply([...])?

thebigk's avatar
Level 13

My bad! Really sorry about that! Yes, it's a method on $thread. :(

It looks like the 'relationship' is doing all the magic behind the scenes. Really appreciate your help, @tykus !

1 like
thebigk's avatar
Level 13
$reply = factory('App\Reply')->make(['user_id' => $user->id, 'thread_id' => $thread->id]);

Explicitly informing the factory to keep the thread_id and user_id did the trick. The factory was whipping up a new user and thread. :)

tykus's avatar

@thebigk - why must you make a Reply instance?

Your test is not simulating the request being made by your app, and might be giving you a level of confidence that is not realistic. If I joined your dev team and looked at your tests, I would expect that your request should include a thread_id and a user_id

thebigk's avatar
Level 13

Hmm. I figured that after posting my reply and actually figuring that out. You are right - the test is actually validating something else, not the request sent by user.

I therefore tried this:

    /** @test */
    public function an_authenticated_user_may_participate_in_forum() {
        $this->withoutExceptionHandling(); // Disable exception handling. Throw the exception at right place
        $user = factory('App\User')->create();
        $this->be($user);

        $thread = factory('App\Thread')->create(); // Thread ID 1
        //$reply = factory('App\Reply')->make();

        $this->post($thread->path() . '/replies', [
            'body' => 'My Reply']);

        $this->get($thread->path())
                ->assertSee('My Reply');

        $this->assertDatabaseHas('replies', ['body' => 'My Reply']);

    }

...and it's worked. I think it's alright now.

tykus's avatar

You will want to also make sure that the body has been persisted with the correct thread_id and user_id - did you read my code snippet earlier? This ensures that the reply is by the expected user and on the expect thread:

    $this->assertDatabaseHas('replies', [
        'thread_id' => $thread->id,
        'user_id' => $user->id,
        'body' => 'My Reply'
        
    ]);
1 like
thebigk's avatar
Level 13

@hju-mikekevin-castro - You should be able to find it in the first few episodes of the create a forum series.

Please or to participate in this conversation.