Mocking, Spying, or Faking your Facades
We will discuss the differences between them while using Laravel Facades, and which one is the best one. Spoiler alert, Fakes are.
Mocking, Spying, and Faking
They all share the same main goal: preventing code from getting executed.
Besides that, they also allow us to make assertions for the number of times they were executed, and with which parameters.
Mocking
By far the most famous one. Before your code gets executed, you have to define the expectations: which method will be called, how many times, and what it should return.
Log::shouldReceive('log')->with('info', 'foo')->once();
...
Log::log('info', 'foo');
Spying
Instead of defining the expectations before running the code, spies replace the target with a stub that we can assert after executing.
Log::spy();
...
Log::log('info', 'foo');
...
Log::shouldHaveReceived('log')->with('info', 'foo')->once();
Fakes
Fakes are swap classes that keep a memory state that you can later assert.
They require custom implementations, unlike Mock/Spy which come out of the box with Mockery.
Log::fake();
...
Log::log('info', 'foo');
...
Log::assertLoggedMessage('info', 'foo');
Organizing your tests
If we follow the convention, we organize our tests into three sections. They go by many names, but in this case, we’ll use “Arrange”, “Act” and “Assert”.
// Arrange
...
// Act
Log::log('info', 'foo');
// Assert
...
If we try to use a Mock, we will be defining the expectations while Arranging. These will state the exact methods that will be called and assert how many times they get called.
Without wanting to, we are asserting while arranging, which leads to unorganized tests that become hard to read.
Is because of this reason that Spies or Fakes are preferred to Mocks, where the assertions come after the Act.
Testing the implementation
Why do we test? Do we write tests so whenever we refactor a line of code, we have to come back and fix the tests? Or do we test so given a certain input, it will produce the same output/side-effects, regardless of the code?
If you answered yes to the latter, it means that your tests check the functionality. This is called Black-box testing.
When you use mocks and spies for your tests, you are also testing implementation (certain method was called with X and Y parameters). This is known as White-box testing.
To follow on the previous examples, let’s say we recently discovered the method Log::info('foo')
, which looks cleaner than Log::log('info', 'foo')
.
When we refactor to use this new method, all tests that were mocking/spying on 'log'
will now break, and must be adjusted, even though the outcome is the same.
For the Fake, we won’t have to do anything. Since internally, info
is calling log('info'
, it will work out of the box. Because of this, fakes allow us to test the state of your class, not its usage.
Faking your code
Don’t limit yourself to only using Laravel’s fakes. You can easily fake a custom service.
First, create a new Fake class that extends the target class,
Then, locate the methods that execute the code you want to avoid executing. For example, on a Mailer, you want to avoid actually sending the email.
Once you identified those methods, you override them to store the data in memory (perhaps a static protected property).
After that, you add some useful assertion methods to make sure the code was executed. You can get inspired by some of Laravel’s assertions.
To use your new fake, simply instance it using the service container.
By the way, Laravel doesn’t ship with a Log fake. The example is using log-fake.
Conclusion
If you want to stay away from testing the inner workings of your class/method, I would recommend taking the time to create fakes instead of using mocks.
They will allow you to refactor the code knowing that if the tests fail, is because the behavior changed, and not the actual implementation.