Mock Functions

Explore the different ways of mocking functions and how we can use them to increase confidence in our code.

Why do we mock functions?

There are three main reasons that we mock functions in our tests.

  • We have to. Sometimes we have no choice but to mock the calling of a function. For example, code that relies on a package connecting to an external service or authentication cannot run properly in our testing environment on its own. In this scenario, we have to mock the function so that our code can run.
  • We want to control the function call or return value. If we are testing error handling, it may be easiest to cause the error by just telling Jest, “Have this function throw an error,” rather than creating the real-world behavior to throw the error. If we want to test that our logic works with the return value of a function as expected, it’s often easiest to do that by telling Jest, “Have this function return this value so that we know what we expect our code to do.” In both these situations, it is not the function being mocked that we are trying to test but rather how the rest of our code interacts with the running of the function. Gaining control over the function call and return value makes testing different scenarios massively easier.
  • We want insight into the calling of the function. This is particularly useful in client-side code, but it’s also useful in any scenario where we are passing a function into our code as an argument. Additionally, it’s helpful in situations where we are less concerned with what the function does and more concerned with how our code calls it. For instance, we may want to know that our server repository layer does in fact call axis.get, or that setValue is called with the expected new value. What axios.get and setValue then go on to do is irrelevant. We just need to know that our code is using these as we expect them to.

Leveraging mock functions

We have two options for mocking functions: creating a new Jest mock function using jest.fn() and spying on an existing function using jest.spyOn().

The jest.fn() function

The jest.fn() function creates an actual function that Jest has special insight into. This means we can set it to a variable and then use this variable to access these insights. For example, if we have a component that accepts a click handler, we can pass a mock function created with jest.fn() to the component and then make assertions on it to validate our expectations.

import { Button } from './Button';

describe('Button', () => {
  test('calls the click handler on click', () => {
    const mockClickHandler = jest.fn();
    const { getByRole } = render(
      <Button onClick={mockClickHandler} />
    );

    const btn = getByRole('button');
    btn.click();
    
    expect(mockClickHandler).toHaveBeenCalled();
  });
}); 

If we don’t need to make assertions on the mocked function but just need something valid to pass into our component, we can directly pass jest.fn() in. However, we’ll lose access to it in the testing of our code’s behavior:

import { Button } from './Button';

describe('Button', () => {
  test('calls the click handler on click', () => {
    const { getByRole } = render(
      <Button onClick={jest.fn()} />
    );
    const btn = getByRole('button');
    btn.click();
    
    expect(mockClickHandler).toHaveBeenCalled();
  });
}); 

The jest.spyOn() function

Alternatively, we can “spy” on existing functionality. This method works nicely when we need to mock a return value—for instance, mocking the response of an axios request.

The jest.spyOn() function accepts three arguments: an object, a method name, and optionally, an access type. The object is the thing we want to mock, e.g., a module. The method name is a string and should match the module’s method name that we want to mock. Last, the access type can be either get or set, which allows us to spy on getters and setters.

import axios from 'axios';
import userRepository from './UserRepository';

describe('get users', () => {
  test('fetches users', () => {
    const spy = jest.spyOn(axios, 'get');

    spy.mockReturnValueOnce(['user1', 'user2]);
    
    expect(userRepository.getUsers()).toHaveReturned();
  });
}); 

Mock assertions

Jest provides a number of helpful assertions that we can make on our mock functions.

  • .toHaveBeenCalled() tests whether or not the function has been called in our code.
  • .toHaveBeenCalledTimes(number) tests if the function has been called an exact number of times in our code.
  • .toHaveBeenCalledWith(arg1, arg2, ...) tests that a function was called with a particular set of arguments.
  • .toHaveBeenLastCalledWith(arg1, arg2, ...) tests that a function was last called with a specified set of arguments.
  • toHaveBeenNthCalledWith(nthCall, arg1, arg2, ....).
  • .toHaveReturned() tests that a function successfully returned, regardless of return value or times called.
  • .toHaveReturnedTimes(number) tests that a function successfully returned a specified number of times.
  • .toHaveReturnedWith(value) tests that a function returned a specific value.
  • .toHaveLastReturnedWith(value) tests that a function returned a specific value on its last call.
  • .toHaveNthReturnedWith(nthCall, value) tests that a function returned a specific value on a specific run.

In the example below, we can see both jest.spyOn() and jest.fn() in use with several of the above assertions:

Get hands-on with 1200+ tech skills courses.