Mocking Fluent Laravel Facade Chains

Written By Jesse Schutt
Posted on

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 Chains1

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 chain2 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 Mocking3 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() with andReturnSelf() if you want to assert both the overall output and the parameter input of each fluent method.

Go forth and test with confidence!

  1. Yes, I like saying "demeter chains" - It sounds cool.
  2. I did it again. Last time, I promise!
  3. OK, it really seemed appropriate to reference it again. Sorry
  4. Cover Photo Credit