Three types of mocks
Mocking, faking; these might sound like intimidating words if you don't know what they are about, but once you do, you'll be able to improve your testing skills significantly.
Part of "the art of testing" is being able to test code in some level of isolation to make sure a test suite is trustworthy and versatile. These topics are so important that we actually made five or six videos on them in our Testing Laravel course.
In this post, I want to share three ways how you can deal with mocking and faking. Let's dive in!
Laravel's Fakes
Laravel has seven fakes — eight if you count time as well:
- Bus
- Event
- HTTP
- Notification
- Queue
- Storage
- Time
Laravel fakes are useful because they are built-in ways to disable some of the core parts of the framework during testing while still being able to make assertions on them. Here's an example of using the Storage
fake to assert whether a file would have been saved in the correct place if the code was run for real, outside of your test suite:
Storage::fake('public');
$post = BlogPost::factory()->create();
Storage::disk('public')
->assertExists("blog/{$post->slug}.png");
Mockery
Laravel has built-in support for Mockery, a library that allows you to create mocks — fake implementations of a class — on the fly.
Here we create an example of an RssRepository
, so that we won't perform an actual HTTP request, but instead return some dummy data:
$rss = $this->mock(
RssRepository::class,
function (MockInterface $mock) {
$mock
->shouldReceive('fetch')
->andReturn(collect([
new RssEntry(/* … */)
]));
}
);
You can imagine how using mocks can significantly impact the performance and reliability of your test suite.
Handcrafted mocks
Mockery can sometimes feel heavy or complex, depending on your use case. My personal preference is to use handcrafted mocks instead: a different implementation of an existing class, one that you register in Laravel's container when running tests. Here's an example:
class RssRepositoryFake extends RssRepository
{
public function fetch(string $url): Collection
{
return collect([
new RssEntry(/* … */),
]);
}
public static function setUp(): void
{
self::$urls = [];
app()->instance(
RssRepository::class,
new self(),
);
}
}
By cleverly using the service container, we can override our real RssRepository
by one that doesn't actually perform any HTTP requests. If you're curious to learn more about them, you can check out our Testing Laravel course.
What are your thoughts on "Three types of mocks"?