Jest Mock Functions

Jest Mock Functions are functions that simulate the behavior of real functions, allowing you to isolate and test specific units of code by controlling their dependencies and observing their interactions.

Detailed explanation

Mock functions in Jest are a powerful tool for unit testing, enabling developers to isolate the code under test from its dependencies. This isolation is crucial for ensuring that tests are focused, fast, and reliable. By replacing real dependencies with mock functions, you can control the inputs to the code being tested, verify that the code interacts with its dependencies as expected, and avoid external factors that could make tests flaky.

At their core, Jest mock functions are JavaScript functions with added functionality for tracking how they are called, what arguments they receive, and what values they return. Jest provides several methods for creating and configuring mock functions, including jest.fn(), jest.spyOn(), and jest.mock().

jest.fn() creates a brand new mock function. This is useful when you need a simple mock that you can configure with specific return values or implementations. For example:

const myMock = jest.fn();
 
myMock('hello');
myMock('world');
 
console.log(myMock.mock.calls);
// > [ [ 'hello' ], [ 'world' ] ]

jest.spyOn() creates a mock function that wraps an existing function. This allows you to track calls to the original function while still executing its original implementation. This is helpful when you want to verify that a function is called with the correct arguments without completely replacing its behavior.

const myObject = {
  myMethod: (x) => x + 1,
};
 
const spy = jest.spyOn(myObject, 'myMethod');
 
myObject.myMethod(2);
 
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith(2);
expect(myObject.myMethod(2)).toBe(3);

jest.mock() is used to automatically mock modules. This is particularly useful when dealing with complex dependencies or external libraries. Jest will replace the module with a mock implementation, allowing you to control its behavior during testing.

jest.mock('./myModule');
 
import myModule from './myModule';
 
myModule.myFunction.mockReturnValue(42);
 
console.log(myModule.myFunction());
// > 42

Practical Implementation and Best Practices

When using mock functions, it's important to follow some best practices to ensure that your tests are effective and maintainable.

  1. Focus on Interactions: Mock functions are most useful for verifying how the code under test interacts with its dependencies. Focus on asserting that the mock functions are called with the correct arguments and that they return the expected values.

  2. Avoid Over-Mocking: Only mock the dependencies that are necessary to isolate the code under test. Over-mocking can lead to tests that are brittle and don't accurately reflect the behavior of the system.

  3. Use Clear and Descriptive Names: Give your mock functions clear and descriptive names that indicate their purpose. This will make your tests easier to understand and maintain.

  4. Configure Mock Implementations Carefully: When configuring mock functions, be sure to provide realistic return values and implementations. This will help to ensure that your tests accurately simulate the behavior of the real dependencies.

  5. Reset Mocks After Each Test: Use jest.clearAllMocks(), jest.resetAllMocks(), or jest.restoreAllMocks() to reset the state of your mock functions after each test. This will prevent state from leaking between tests and ensure that your tests are independent. clearAllMocks clears the records of calls, resetAllMocks does the same and resets the implementation, and restoreAllMocks removes any mock and restores the original function.

  6. Consider using mockImplementation and mockResolvedValue: For asynchronous functions, mockResolvedValue is a cleaner way to mock the resolved value of a promise. mockImplementation allows you to define a custom function to be executed when the mock is called, providing more control over the mock's behavior.

Common Tools and Techniques

In addition to the core Jest mock function methods, there are several other tools and techniques that can be helpful when working with mock functions.

  • mockReturnValue(value): Sets the return value of the mock function.
  • mockImplementation(fn): Sets the implementation of the mock function.
  • mockResolvedValue(value): Sets the resolved value of a promise returned by the mock function.
  • mockRejectedValue(value): Sets the rejected value of a promise returned by the mock function.
  • .mock property: Provides access to information about how the mock function has been called, including the arguments it has received and the values it has returned.

Example Scenario

Consider a function that fetches user data from an API and displays it on the page. To test this function, you could mock the API call using jest.fn() and configure it to return a specific user object. You could then assert that the function correctly displays the user data on the page.

// userApi.js
export const fetchUser = async (userId) => {
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
};
 
// userDisplay.js
import { fetchUser } from './userApi';
 
export const displayUser = async (userId, displayElement) => {
  const user = await fetchUser(userId);
  displayElement.textContent = `User: ${user.name}, Email: ${user.email}`;
};
 
// userDisplay.test.js
import { displayUser } from './userDisplay';
import * as userApi from './userApi';
 
jest.mock('./userApi');
 
describe('displayUser', () => {
  it('should display user data on the page', async () => {
    const mockUser = { name: 'John Doe', email: '[email protected]' };
    userApi.fetchUser.mockResolvedValue(mockUser);
 
    const displayElement = document.createElement('div');
    await displayUser(123, displayElement);
 
    expect(displayElement.textContent).toBe('User: John Doe, Email: [email protected]');
    expect(userApi.fetchUser).toHaveBeenCalledWith(123);
  });
});

In this example, jest.mock('./userApi') replaces the real userApi module with a mock. userApi.fetchUser.mockResolvedValue(mockUser) configures the mock fetchUser function to return a promise that resolves with the mockUser object. The test then asserts that the displayUser function correctly displays the user data and that the fetchUser function is called with the correct user ID. This demonstrates how mock functions can be used to isolate and test specific units of code by controlling their dependencies.

By mastering Jest mock functions, developers can write more effective and reliable unit tests, leading to higher-quality software.

Further reading