Stubbing Eloquent Relations for Faster Tests
When you’re trying to test methods on an Eloquent model, you often need to hit the database to really test your code.
But sometimes the functionality you’re testing doesn’t really depend on database features. Is there any way to test that stuff without hitting the database?
Testing against the database
Say we had Albums
with Songs
and we wanted to test that we could calculate the total duration of an album.
Our test might look something like this:
A simple implementation of $album->duration
could look like this:
If we run this test everything will pass, but at least on my machine, it takes about ~90ms on average to run.
For tests that really need to hit the database for me to verify the behavior I’m trying to verify, I’m totally fine with that.
But in this case, we aren’t trying to test a specific set of query scopes, or verify that something is being saved, we just want to sum some numbers!
There must be another way!
In-memory relationships
When you eager load a relationship, using the $album->songs
property shorthand doesn’t perform a new query; instead it fetches the Songs
from a memoized property on the Album
.
We can pretend to eager load a relationship manually using the setRelation
method:
Now we can get rid of the DatabaseMigrations
trait and use make
to build our Album
instead of create
, leaving us with a test that looks like this:
Running this test still passes, but this time it only takes about 3ms to finish! That’s thirty times faster.
Nothing is free
As with anything, there’s a cost to using this approach.
If we changed our implementation to sum the song durations in the database, our new test would fail:
So while the performance improvements are insane, they come at the cost of coupling us to a particular style of implementation, and make things a little less flexible to refactor.
Either way, it’s a cool trick to keep in mind if your test suite is starting to slow down, and often the benefits will outweigh the cost.