/ #jest #testing 

Jest set, clear and reset mock/spy/stub implementation

Curious about Advanced Jest Testing Features?

Want to level up your JavaScript testing fundamentals and leverage Jest's best features?

The Jest Handbook is for you.

I want this

Between test runs we need mocked/spied on imports and functions to be reset so that assertions don’t fail due to stale calls (from a previous test). This is a way to mitigate what little statefulness is in the system.

In unit tests of complex systems, it’s not always possible to keep business logic in pure functions, where the only input are the parameters and the only output is the return value. Systems are inherently side-effectful (things that are not parameters or output values). HTTP requests, database reads and writes are side-effects that are crucial to writing applications.

This is why we want to be able to set and modify the implementation and return value of functions in Jest.

This post goes through how to set, reset and clear mocks, stubs and spies in Jest using techniques such as the beforeEach hook and methods such as jest.clearAllMocks and jest.resetAllMocks. We’ll also see how to update a mock or spy’s implementation with jest.fn().mockImplementation(), as well as mockReturnValue and mockResolvedValue. This post is a reference to be able to discern when to use each of these.

Table of Contents

Reset/Clear with beforeEach/beforeAll and clearAllMocks/resetAllMocks

Assuming we have a global stub or spy that is potentially called mutliple times throughout our tests.

const mockFn = jest.fn();

function fnUnderTest(args1) {
  mockFn(args1);
}

test('Testing once', () => {
  fnUnderTest('first-call');
  expect(mockFn).toHaveBeenCalledWith('first-call');
  expect(mockFn).toHaveBeenCalledTimes(1);
});
test('Testing twice', () => {
  fnUnderTest('second-call');
  expect(mockFn).toHaveBeenCalledWith('second-call');
  expect(mockFn).toHaveBeenCalledTimes(1);
});

Clearing Jest Mocks with .mockClear(), jest.clearAllMocks() and beforeEach

TODO: Running the examples yarn test src/beforeeach-clearallmocks.test.js

Running the above Jest tests yield the following output:

$ jest src/beforeeach-clearallmocks.test.js
 FAIL  src/beforeeach-clearallmocks.test.js
  ✓ Testing once (4ms)
  ✕ Testing twice (3ms)

  ● Testing twice

    expect(jest.fn()).toHaveBeenCalledTimes(expected)

    Expected number of calls: 1
    Received number of calls: 2

      13 |   fnUnderTest('second-call');
      14 |   expect(mockFn).toHaveBeenCalledWith('second-call');
    > 15 |   expect(mockFn).toHaveBeenCalledTimes(1);
         |                  ^
      16 | });
      17 |

      at Object.toHaveBeenCalledTimes (src/beforeeach-clearallmocks.test.js:15:18)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 passed, 2 total

In this case, mockFn has been called twice, to fix this, we should clear the mock.

We can achieve this as follows by only changing the second file:

test('Testing twice', () => {
  mockFn.mockClear();
  fnUnderTest('second-call');
  expect(mockFn).toHaveBeenCalledWith('second-call');
  expect(mockFn).toHaveBeenCalledTimes(1);
});

Another way to do it is to clearAllMocks, this will mean that between tests, the stubs/mocks/spies are cleared, no matter which mock we’re using.

test('Testing twice', () => {
  jest.clearAllMocks();
  fnUnderTest('second-call');
  expect(mockFn).toHaveBeenCalledWith('second-call');
  expect(mockFn).toHaveBeenCalledTimes(1);
});

Technically, we’ve only been changing the 2nd test, although they should be reorderable in principle. In order to run a piece of code before every test, Jest has a beforeEach hook, which we can use as follows.

// mock + code under test definition
beforeEach(() => {
  jest.clearAllMocks();
});
// tests

See Running the examples to get set up, then run: npm test src/beforeeach-clearallmocks.test.js

As per the Jest documentation:

jest.clearAllMocks()

Clears the mock.calls and mock.instances properties of all mocks. Equivalent to calling .mockClear() on every mocked function.

Jest mockReset/resetAllMocks vs mockClear/clearAllMocks

We’ve just seen the clearAllMocks definition as per the Jest docs, here’s the mockReset() definition:

mockFn.mockReset()

Does everything that mockFn.mockClear() does, and also removes any mocked return values or implementations.

This is useful when you want to completely reset a mock back to its initial state. (Note that resetting a spy will result in a function with no return value).

Here’s the explanation:

  • mockClear clears only data pertaining to mock calls, which means we get a fresh dataset to assert over with toHaveBeenX methods.
  • mockReset resets to mock to its initial implementation, on a spy makes the implementation be a noop (function that does nothing).

I’ve personally not found mockReset’s use case to be too compelling. In situation where one might use resetAllMocks/mockReset, I opt for mockImplementationOnce/mockReturnValueOnce/mockResolvedValueOnce in order to set the behaviour of the stub for a specific test instead of resetting said mock.

Setting a mock/stub/spy implementation with mockImplementation/mockImplementationOnce

We’ve looked at how to make sure call information is cleared between tests using jest.clearAllMocks().

Now we’ll see how to set the implementation of a mock or spy using mockImplementation and mockImplementationOnce.

This is useful when the code under tests relies on the output of a mocked function. In that case, overriding the implementation allows us to create test cases that cover the relevant code paths.

Given a function that returns a string based on the output of another function:

const mockFn = jest.fn();

function fnUnderTest(args1) {
  return mockFn(args1) ? 'Truth': 'Falsehood';
}

We could write the following tests using mockImplementation:

test('It should return correct output on true response from mockFn', () => {
  mockFn.mockImplementation(() => true);
  expect(fnUnderTest('will-it-work')).toEqual('Truth');
});
test('It should return correct output on false response from mockFn', () => {
  mockFn.mockImplementation(() => false);
  expect(fnUnderTest('will-it-work')).toEqual('Falsehood');
});

Our tests pass with the following output:

See Running the examples to get set up, then run: npm test src/mockimplementation.test.js

$ jest src/mockimplementation.test.js
 PASS  src/mockimplementation.test.js
  ✓ It should return correct output on true response from mockFn (4ms)
  ✓ It should return correct output on false response from mockFn

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total

We can override behaviour for a single test, using mockImplementationOnce, which would lead to the following tests

test('It should return correct output on true response from mockFn', () => {
  mockFn.mockImplementationOnce(() => true);
  expect(fnUnderTest('will-it-work')).toEqual('Truth');
});
test('It should return correct output on false response from mockFn', () => {
  mockFn.mockImplementationOnce(() => false);
  expect(fnUnderTest('will-it-work')).toEqual('Falsehood');
});

mockImplementationOnce for multiple subsequent calls

mockImplementationOnce can also be used to mock multiple subsequent calls.

const fetch = jest.fn();

async function data() {
  const data = await fetch('/endpoint-1');
  await fetch(`/endpoint-2/${data.id}`, {
    method: 'POST'
  });
}

test('It should call endpoint-1 followed by POST to endpoint-2 with id', async () => {
  fetch.mockImplementationOnce(async () => ({id: 'my-id'}));
  fetch.mockImplementationOnce(async () => {});
  await data();
  expect(fetch).toHaveBeenCalledTimes(2);
  expect(fetch).toHaveBeenCalledWith('/endpoint-1');
  expect(fetch).toHaveBeenCalledWith('/endpoint-2/my-id', {
    method: 'POST'
  });
});

See Running the examples to get set up, then run: npm test src/mockimplementationonce-multiple.test.js

The test passes successfully. The output is as follows:

$ jest src/mockimplementationonce-multiple.test.js
 PASS  src/mockimplementationonce-multiple.test.js
  ✓ It should call endpoint-1 followed by POST to endpoint-2 with id (5ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

Overriding a synchronous mock/spy/stub’s output with mockReturnValue/mockReturnValueOnce

We can set a mock’s synchronous output using mockReturnValue and mockReturnValueOnce.

As we can see in this example, the order in which we call mockReturnValueOnce on the mock affect the order in which the given values are output. Namely, they’re in the same order, so to mock the first call, use the first mockReturnValueOnce, for the second, the secont call and so on.

What we also observe is that mockReturnValue is used when the outputs set through mockReturnValueOnce are exhausted.

const format = jest.fn();
function getName(firstName, ...otherNames) {
  const restOfNames = otherNames.reduce(
    (acc, curr) => (acc ? `${acc} ${format(curr)}` : format(curr)),
    ''
  );
  return `${format(firstName)} ${restOfNames}`;
}

test('it should work for multiple calls', () => {
  format.mockReturnValue('default-format-output');
  format.mockReturnValueOnce('formatted-other-name-1');
  format.mockReturnValueOnce('formatted-other-name-2');
  format.mockReturnValueOnce('formatted-first-name');

  const actual = getName('first-name', 'other-name-1', 'other-name-2');

  expect(format).toHaveBeenCalledTimes(3);
  expect(actual).toEqual(
    'formatted-first-name formatted-other-name-1 formatted-other-name-2'
  );

  expect(format()).toEqual('default-format-output')
});

See Running the examples to get set up, then run: npm test src/mockreturnvalue.test.js

Overriding an async mock/spy/stub’s output with mockResolvedValue/mockResolvedValueOnce

In a way reminiscent of how mockReturnValue/mockReturnValueOnce can help simplify our tests in the synchronous mock implementation case. mockResolvedValue/mockResolvedValueOnce can help us simplify our tests when setting the implementation of an asynchronous mock.

We can set an asynchronous mock’s resolved output (a Promise that resolves to the value) using mockResolvedValue and mockResolvedValueOnce.

The order in which mockResolvedValueOnce are called on the mock also map to the order of the output of the mock.

mockResolvedValue is used when the outputs set through mockResolvedValueOnce are exhausted.

const fetch = jest.fn();

async function data() {
  const data = await fetch('/endpoint-1');
  await fetch(`/endpoint-2/${data.id}`, {
    method: 'POST'
  });
}

test('Only mockResolvedValueOnce should work (in order)', async () => {
  fetch.mockResolvedValue({data: {}});
  fetch.mockResolvedValueOnce({id: 'my-id'});
  fetch.mockResolvedValueOnce({});
  await data();
  expect(fetch).toHaveBeenCalledTimes(2);
  expect(fetch).toHaveBeenCalledWith('/endpoint-1');
  expect(fetch).toHaveBeenCalledWith('/endpoint-2/my-id', {
    method: 'POST'
  });

  expect(await fetch()).toEqual({
    data: {}
  });
});

See Running the examples to get set up, then run: npm test src/mockresolvedvalue.test.js

Running the examples

Clone github.com/HugoDF/jest-set-clear-reset-stub.

Run yarn install or npm install (if you’re using npm replace instance of yarn with npm run in commands).

Further Reading

To understand which assertions can be used on mocks and stubs see the following posts:

More foundational reading for Mock Functions and spies in Jest:

unsplash-logoJose Antonio Gallego Vázquez

Author

Hugo Di Francesco

Co-author of "Professional JavaScript" with Packt. He runs the Code with Hugo website helping over 100,000 developers every month and holds an MEng in Mathematical Computation from University College London (UCL). He has used JavaScript extensively to create scalable and performant platforms at companies such as Canon and Elsevier.

Curious about Advanced Jest Testing Features?

Want to level up your JavaScript testing fundamentals and leverage Jest's best features? The Jest Handbook is for you.