skay-
skay-

Reputation: 1576

Testing oneToMany relationship Laravel, Eloquent doesn't work

I can't get my head around this:

$fragment = factory(Fragment::class)->create();

$this->assertCount(0, $fragment->values);

fragment->fetch(); // updates the 'values' by adding one Value object.

var_dump($fragment->id); // i.e. 6
var_dump(Fragment::first()->id); // 6 

var_dump($fragment->values->count());  // 0
var_dump(Fragment::first()->values->count()); // 1

$this->assertCount(1, $fragment->values);

I use DatabaseTranscations, so after a Fragment is created, there is always one and only one. Thus, $fragment and Fragment::first() are the exact same instance. Yet... the values relationship is different. How can this be the case?

Note that this happens only during testing, when I manually test this through my controller (and the values are passed to the blade template page) it works just fine. I am confused :S.

Any ideas?

Upvotes: 0

Views: 152

Answers (1)

patricus
patricus

Reputation: 62228

Relationship attributes ($fragment->values) are only loaded once. They are not kept up to date when you add or delete items from the relationship. They do not hit the database every time to check for changes.

Your second line is $this->assertCount(0, $fragment->values);. Accessing $fragment->values here lazy loads the relationship, and as your assert proves, it is empty.

You then call $fragment->fetch(), in which your comment says it adds a Value object to the fragment. However, your relationship attribute ($fragment->values) has already been loaded from the previous statement, so it will not reflect the additional Value object you added to the relationship.

Therefore, even after the call to fetch(), $fragment->values is still going to be an empty collection. Fragment::first()->values will contain the newly related Value though, because it is getting a new instance of the Fragment, and when it loads the values for the first time, it will pick up the related Value.

When you need to reload the relationship, you can use the load() method. If you add this after your call to fetch() (or put it in your fetch() method, whichever makes sense for you), your test will work fine.

$fragment = factory(Fragment::class)->create();

$this->assertCount(0, $fragment->values);

$fragment->fetch(); // updates the 'values' by adding one Value object.

var_dump($fragment->id); // i.e. 6
var_dump(Fragment::first()->id); // 6 

var_dump($fragment->values->count());  // 0

// reload the values relationship
$fragment->load('values');

var_dump($fragment->values->count());  // 1
var_dump(Fragment::first()->values->count()); // 1

$this->assertCount(1, $fragment->values);

The other option you have is to use the relationship query by accessing the relationship method instead of the relationship attribute. If you do $fragment->values()->count() (note: values(), not values), that will hit the database every time and always return the current count.

Upvotes: 1

Related Questions