Mocking Fluent Laravel Facade Chains
Have you ever needed to test that a chain of methods on a facade has the right parameters along each step?
Read on and I'll show you how to do it!
The ability to mock facades within Laravel is a great help when it comes to testing code by simulating the functionality of the facade.
Several of the facades have specific methods for checking they were properly triggered during a test while bypassing their normal functionality. For example, setting up "fakes" on either the Mail or Notification facades disables the distribution of mail/notifications and captures the output so we can check if it's being sent to the correct user.
Notification::fake();
// Perform order shipping...
Notification::assertSentTo(
$user,
OrderShipped::class,
function ($notification, $channels) use ($order) {
return $notification->order->id === $order->id;
}
);
The Storage fake will intercept files and place them in a temporary directory providing us a way to test things like file uploads.
Storage::fake('avatars');
$response = $this->json('POST', '/avatar', [
'avatar' => UploadedFile::fake()->image('avatar.jpg')
]);
// Assert the file was stored...
Storage::disk('avatars')->assertExists('avatar.jpg');
Testing facade methods
If we need a more specific way to test arbitrary methods called on a facade we can use the shouldReceive()
method. This method will convert the facade under test to an instance of Mockery, which is essentially a class upon which we can call facade methods on. Once our facade has been converted to a mock we have a whole bunch of new assertions at our disposal.
// In our laravel code, perhaps a controller:
View::addNamespace('custom', '/../../views');
// In our test:
View::shouldReceive('addNamespace')
->with('custom', '/../../views')
->once();
Frequently I will fluently chain methods onto a facade to accomplish several actions in one fell swoop.
/**
* @param string $file
*
* @return bool
*/
protected function fileExists(string $file): bool
{
return Storage::disk('base')->exists($file);
}
Demeter chains
Mockery gives us a few options when testing fluent chains. If we only want the overall output of the facade calls we can use "demeter chains." This kind of assertion will skip over all parameters on the fluent chain except the very last. It's convenient for situations when we don't care what happens to the data during its transformation, but we do care for what the facade outputs.
Storage::shouldReceive('disk->exists')
->with('image.jpg')
->once()
->andReturn(false);
Testing each fluent method
But what if we do want to assert that each step in the chain has specific parameters? Or that the methods are called X times? In these cases we need something other than the demeter chain syntax.
// Facade with several methods requiring specific data
View::addNamespace('custom', '/../../views')
->make("custom::demo-view")
->with(['additional_data' => 'here'])
->render();
Fortunately, Laravel exposes the andReturnSelf() method upon which we can start another assertion loop.
View::shouldReceive('addNamespace')
->with('custom', '/../../views')
->once()
->andReturnSelf()
->shouldReceive('make')
->with('custom::demo-view')
->once()
->andReturnSelf()
->shouldReceive('with')
->with([
'additional_data' => 'here'
])->once()
->andReturnSelf()
->shouldReceive('render')
->once();
Which approach is appropriate?
The mock methods on Laravel facades are very helpful ways to test that your code does what you expect. You have a few options at your disposal:
- Use Fakes if you want to disable functionality of a specific facade and utilize Laravel's custom assertions.
- Use
ShouldReceive()
with Demeter Chain Mocking if you don't need to check the parameter input of the fluent facade calls but only want to assert the overall output. - Use
ShouldReceive()
withandReturnSelf()
if you want to assert both the overall output and the parameter input of each fluent method.
Go forth and test with confidence!
Want to read more tips and insights on working with a Laravel development team that wants to help your organization grow for good? Sign up for our bimonthly newsletter.
By Jesse Schutt
Director of Engineering
Jesse is our resident woodworker. His signature is to find the deeper meaning in a project and the right tool for the job.