Laravel Mocking: What is it and When Should You Use It?
Mocking in tests means replacing a dependency with a controlled test double so you can focus on the code that uses it. In Laravel, a mock can stand in for a class that the code under test depends on, letting you specify its behaviour and responses while still testing your own code.
A mock is one type of test double. Others include dummies, fakes, stubs and spies.
This follows our guide to writing automated tests, which covers the wider process of setting up, running and asserting against the code under test.
When a car is being built, and the engine (a feature of the car) needs testing, they don’t start by popping the engine into a fully made car. Instead, it is attached to a tool for testing engines called an engine test stand. The engine test stand can communicate with the engine (like a car would), and has sensors to analyse what the engine does in response. Mocks are similar to the engine test stand in that they isolate functionality (the engine/the code to be tested) for specific individual testing, neither are used in a production environment, and both replace functionality that would otherwise be available (the rest of the car/specific dependencies).
One common early mistake that can be made is believing that the mock replaces the code that is being isolated for testing. The resulting test will instantiate the mock class, run the configured expected behaviours on the mock class, and return a successful result, all without running or asserting against the code intended to be tested.
Why Use Mocks in Laravel Testing?
Isolating code is a very important aspect of writing tests because one significant purpose of having a test suite is the ability to quickly identify the specific location in the codebase that is not functioning as expected. A very good way to achieve this is by testing the codebase in small, modular pieces, so that when there is a test failure you know precisely which part of the site needs to be debugged. As a tool that aids engineers with isolating sections of the codebase, mocking is extremely useful for this.
Using mocks can also provide engineers with a faster feedback loop when debugging or developing sites. Mocks provide the ability to run functionality without also having to instantiate or run dependencies, which in turn reduces the amount of code necessary to run a test and results in a faster overall test suite.
A major advantage of using mocks is that they can be used for preventing tests from triggering unwanted behaviours, for example sending emails or triggering live API endpoints. By replacing the class responsible for the unwanted behaviour with a mock, engineers can test the route and logic surrounding the behaviour, without triggering the unwanted behaviour itself.
Mocks also allow us to test expected failures, for example a database timeout or insufficient funds when using a payment gateway. Behaviours like these often cannot be purposefully triggered, but by replacing the responsible class with a mock that is set up to respond as if this behaviour has been triggered, an engineer can test how the codebase would respond if this behaviour did happen.
Mocks are particularly useful when an engineer needs to isolate a feature from a dependency for a focused test.
There are also drawbacks to using mocks, particularly in cases where there is an over-dependence on mocking. Mocking creates a greater overhead for engineers, as mocks need to be repeated and configured across multiple tests and increase time necessary for development and review. Mocking can result in the test becoming more brittle, because mocks have specific behaviours and responses configured, and the test will still pass even if the underlying behaviour of the mocked class changes (e.g. if an API returns a different result than expected).
When should you use a mock?
Not all tests will require mocks, and knowing when to utilise a mock is a matter of practice. There are certain circumstances where mocks are particularly useful, and these will be covered below.
Mocking notifications
As has been mentioned, it is common in tests to mock the class responsible for sending notifications so that live notifications are not triggered when running the test suite. Mocking this class allows the engineer to test that notifications are triggered in the correct circumstance and with the correct details, without having to manually send and check the notification.
Mocking external APIs and services
For similar reasons, mocks are often used to replace classes that make calls to external APIs and services. It’s important to mock external services and APIs as the primary purpose of the test suite is to indicate issues within your own code, and if the tests fail due to errors in external APIs or services they are not as suited for this purpose. External services and APIs also might come with additional drawbacks, for example if an external API has rate limiting this might result in unexpected failures in the tests, and many APIs and services are paid services which could incur additional business costs for increased usage.
Mocking files and cache
An area where mocking can be useful but is not technically necessary is when testing code that interacts with files or the cache. A careful engineer can use real files or cache in a separate test environment with appropriate setup and teardown, but the test must not touch live data. Where the test does not need real storage or cache behaviour, a fake or mock of the handler can be useful.
Mocking resource-heavy sections of code
A less obvious use of mocks is to avoid running resource-heavy sections of the code in tests when it is not necessary for testing the targeted behaviour. By mocking a resource-heavy class, an engineer can reduce the time it takes to run tests for code that otherwise would need the resource-heavy dependency to run. This allows an engineer to keep a test suite performant and the tests focused on specific behaviour.
How does mocking affect code structure?
Writing tests for code without ever changing the way the code is written is theoretically possible, but the reality is that well-organised, modular code is easier to write tests for. This is largely due to the fact that tests target specific behaviour in the codebase, and if that behaviour is organised into a class that is responsible for only that behaviour, and the class is loosely coupled with other services and dependencies, less code will need to be instantiated and run to test the behaviour of the code.
Using mocks to isolate areas of the codebase can encourage better organised classes. This can encourage dependency injection, which makes it easier to replace dependencies with mocks, and supports the single responsibility principle.
What is a partial mock?
One type of mock is the partial mock, and this is where a class is mocked on specific methods but retains the functionality of other methods in the class.
Partial mocks can be a code smell when used heavily, so it is worth checking whether the class has too many responsibilities. If the code being tested is tightly coupled with the code that needs to be mocked, that is often an indication that the code should be extracted into multiple classes that each have a single responsibility. By doing this, engineers will end up with a “thin” layer class that can be mocked and is used only for e.g. interacting with an API, and the logic responsible for other behaviour is held in a different class that can then be tested.
Using mocks effectively in tests
Using mocks allows engineers to test more of the codebase, more concisely, without triggering unwanted behaviours. Mocking can speed up a test suite, make individual tests more focused and shorten the feedback loop when debugging. It encourages well organised, modular code, and use of dependency injection. Understanding mocks is key to having a robust, comprehensive test suite, and it is well worth the additional time and skill necessary to implement.