Mocking dependencies of your code is one of the fundamental aspects of software testing. It’s a powerful method that allows developers to gain control over a certain part of your code but can also result in creating an isolated environment for your test if done too much. This will make your tests less reliable and create a false sense of security, which is exactly not what we want.
The key is to be thoughtful of what you’re mocking and for what reason you’re mocking it. If you’re e.g. mocking child components in React tests just because they’re annoying, you’re actively diminishing the representativeness of your tests. This is exactly not what we want. To help you with mocking, this article will share three tips for when it makes sense to mock and how to approach mocking in your tests.
If running a piece of code is not related to how the users interact with it but do require quite some overhead, it’s a perfect candidate for mocking it away. Something like expensive calculations in the background or loading an entire sidebar is not relevant for when you’re verifying whether something is working properly on the main screen.
The most important question to ask yourself is whether the code you are mocking influences the representability of your tests. If not, then mocking is perfectly fine. But if it does, then you shouldn’t mock it even if it introduced a (significant) overhead in your tests. This differs per test suite or even test case as it depends on the relationships between pieces of code in your codebase. Therefore this should be analysed on a test-by-test basis.
The next scenario is when the code does (directly) affect the results of your tests, but you want control over the different scenarios that it causes to verify them. This can be code that interacts with external APIs, data fetching, props, and so on. If you want to verify different flows of your application or component based on whether e.g. errors happen or not, then you want it to be deterministic across CI test runs and not flake every other time.
To make sure that happens, you should mock it and apply the appropriate behaviour based on the flow you’re testing. It makes sense to take control of the different flows if your goal is to verify behaviour based on those different flows. The main reason for mocking a piece of code in this scenario is to remove non-deterministic behaviour from your tests and make them more stable.
Another piece of advice is to keep mocks to a minimum per test. Try to only verify what’s necessary per test and make sure to do it well. This is not quite about how to handle a specific scenario and whether it makes sense to mock, but rather more general advice to set you up for success.
One of the main reasons that we mock more than necessary is because we are trying to do too much in a single test. The more you try to verify in one test, the more different pieces of code the test will trigger, the more you have to mock, and the more unreliable your tests will become.
A good rule of thumb is to make sure every test only verifies one thing and verifies it well. Doing so will make sure you only mock what is necessary and avoid making your tests unreliable that way.
In this article to help you with mocking, I shared three tips for when it makes sense to mock and how to approach mocking in your tests. Code that is irrelevant and resource-heavy can be mocked away because they do not affect the reliability of your test. The same goes for code that is relevant and you want control over, but rather for the reason that it removes non-deterministic behaviour from your tests and increases stability. Lastly, a general piece of advice is to keep mocks to a minimum per tests by verifying only what is necessary.