4.2 Mocking synchronous and asynchronous output

In the same way the previous section showed how to check error output of synchronous and asynchronous functions, this section will tackle dealing with outputting data for stubbed functions in the sync and async case.

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');
});

The test output is as follows.

npx jest src/04.02-mock-return-value.test.js
 PASS  src/04.02-mock-return-value.test.js
  ✓ it should work for multiple calls (4ms)

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

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: {}
  });
});

The test output is as follows.

npx jest src/04.02-mock-resolved-value.test.js
 PASS  src/04.02-mock-resolved-value.test.js
  ✓ Only mockResolvedValueOnce should work (in order) (7ms)

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

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:

npx jest src/04.02-mock-implementation-sync.test.js
 PASS  src/04.02-mock-implementation-sync.test.js
  ✓ It should return correct output on true response from mockFn (3ms)
  ✓ It should return correct output on false response from mockFn (1ms)

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');
});

This passes as well with the following output.

npx jest src/04.02-mock-implementation-once-sync.test.js
 PASS  src/04.02-mock-implementation-once-sync.test.js
  ✓ It should return correct output on true response from mockFn (3ms)
  ✓ It should return correct output on false response from mockFn

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

mockImplementationOnce for async calls and multiple subsequent calls

mockImplementationOnce can be used to mock multiple subsequent calls. The mockImplementationOnce-ed function will run the mock implementation in the order it is set.

For example.

const stub = jest.fn();
// fn1 and fn2 defined somewhere
stub.mockImplementationOnce(fn1);
stub.mockImplementationOnce(fn2);

stub(); // calls fn1
stub(); // calls fn2

By passing an async function to mockImplementation/mockImplementationOnce we can mock async data output.

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'
  });
});

The test passes successfully. The output is as follows:

npx jest src/04.02-multi-mock-implementation-once-async.test.js
 PASS  src/04.02-multi-mock-implementation-once-async.test.js
  ✓ It should call endpoint-1 followed by POST to endpoint-2 with id (7ms)

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

We’ve seen how to use mockReturnValue and mockResolvedValue to make the output of stubs match the expected output from synchronous and asynchronous functions.

The next section will tackle how to programmatically fail tests in Jest.

Jump to table of contents