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.