2.1 Set, Clear and Reset Mocks and Spies
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 section 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
.
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
Running the above Jest tests yield the following output:
npx jest src/02.01-beforeeach-clearallmocks.test.js
FAIL src/02.01-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/02.01-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
As per the Jest documentation:
jest.clearAllMocks()
Clears the
mock.calls
andmock.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 withtoHaveBeenX
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.
Simple setting of mocked function output with mockImplementation
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/02.01-mockimplementation.test.js
PASS src/02.01-mockimplementation.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
Snapshots: 0 total
Time: 1.738s
Ran all test suites matching /src\/02.01-mockimplementation.test.js/i.
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');
});
References
Foundational reading for Mock Functions and spies in Jest:
We’ve now seen how tests can be setup and cleared to facilitate testing.
The next section will look at what assertions can be done over stubs and spies.