Reactive Stack

Mocks Are Overrated And What To Do About It

Cover Image for Mocks Are Overrated And What To Do About It
Benoit Tremblay
Benoit Tremblay

Mocks can be more harmful than do good and let me tell you why. When you are trying to write a unit test, you want to use the smallest unit to run the testing code. Because of that, you might want to make sure only a single component or a single file is used for this test. The reasoning is that if something fails, you want only what is testing this specific thing to fail so that you can find the issue quickly. It works great for leaf components, those that don't depend on any other components.

As soon as you start testing something that integrate multiple components together, you might be tempted to find a way to test only that specific component. To do so, you need to either do shallow rendering or mock your other components. The issue here is with both solution, your test becomes a lot less reliable because it no longer works like the real component. All you did was write the same code twice. Once as the test and once as the implementation. Your components in an app should not be treated like an API because how they are split is just an implementation detail. It might change and it does not matter to the user. He does not see a difference.

The biggest issue with mocking is now it ties your code to the implementation. As soon as you change something, it is going to break your tests because it no longer uses what the mock was expecting. The only reason why this was important before was the speed of the tests. You don't want the test suite to take forever to run. With the tools that we now have in JavaScript, this is no longer an issue. We can run all our tests with the JSDOM almost as fast as if we had no DOM at all.

Mocks are mainly useful when you want to mock an external dependency like an API or an external provider. The best case scenario is if you have an API that you want easily replicate the request and response. In those cases, there is a great library called Mock Service Worker (MSW) that let you do that super easily.

Here is how it works using an example from React Testing Library documentation:

import React from 'react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import Fetch from '../fetch';

const server = setupServer(
  rest.get('/greeting', (req, res, ctx) => {
    return res(ctx.json({ greeting: 'hello there' }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('loads and displays greeting', async () => {
  render(<Fetch url="/greeting" />);

  fireEvent.click(screen.getByText('Load Greeting'));

  await waitFor(() => screen.getByRole('heading'));

  expect(screen.getByRole('heading')).toHaveTextContent('hello there');
  expect(screen.getByRole('button')).toHaveAttribute('disabled');
});

When this is not an option because you are not using a REST API or it is too complex, mocking the function doing the call to the API is a great solution. However, do not try to mock the library. It is often not built to be mocked easily anyways. Mock your own functions calling the API or external provider. For example, if you were using the Firebase Auth library and you wanted to mock the login, you don't want to mock the library call firebase.auth().signInWithPassword(email, password). You want to wrap those calls within your own API functions. Mocking will be much easier to do that way.

If you mock something you don't control, you will most likely regret it.

The second most common reason to mock is when you are testing a callback function to your component and you want to know if it has been called correctly. However, this is quite often not the right approach unless you are building a library or a re-usable component that will be used in many context. When possible, you should be testing the behavior and if the content of the page change correctly, not the implementation. When clicking the delete button, the user don't care about the callback but he does care if the confirmation message is shown.

In what scenarios are you using mocks? Is it really necessary?

Want More Content About React.js?
Subscribe to Our Newsletter

* indicates required

We will never spam you.