I am learning Jest and modern JavaScript, and am trying to test a piece of code. My tests are grouped within a describe()
container, and I would like to reset mocks (and mock counts) between tests. There seem to be some questions on Stack Overflow that deal with resetting or clearing mock data, but they do not seem to work in my case, and I am too new to JS to understand what the differences might be.
Here is my SUT (in source.js
):
module.exports = async function(delay) {
let query;
let ok;
for (let i = 0; i < 5; i++) {
query = context.functions.execute('getNextQuery');
if (query) {
ok = context.functions.execute('runQuery', query);
if (ok) {
context.functions.execute('markQueryAsRun', query.id);
}
} else {
break;
}
// Be nice to the API
await sleep(delay);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
};
And here are the tests:
const findAndRunQueries = require('./source');
describe('Some tests for findAndRunQueries', () => {
let globalMocks = {};
// context.functions.execute() becomes a mock
global.context = {
functions: {
execute: jest.fn((funcName, ...params) => {
// This calls a mock that we set up per test
return globalMocks[funcName](...params);
})
}
};
test('No queries need to be run', async () => {
// Don't need to mock other funcs if this one is falsey
setGlobalMock('getNextQuery', () => {
return null;
});
await findAndRunQueries(0);
// Ensure only getNextQuery is called
expect(getNthMockFunctionCall(0)[0]).toBe('getNextQuery');
expect(countMockFunctionCalls()).toBe(1);
});
test('One query needs to be run', async () => {
let callCount = 0;
// Return an object on the first run, null on the second
setGlobalMock('getNextQuery', () => {
if (callCount++ === 0) {
return {
something: 'fixme'
};
} else {
return null;
}
});
setGlobalMock('runQuery', (query) => {
return true;
});
setGlobalMock('markQueryAsRun', (queryId) => {
// Does not need to return anything
});
await findAndRunQueries(0);
// Ensure each func is called
expect(getNthMockFunctionCall(0)[0]).toBe('getNextQuery');
expect(getNthMockFunctionCall(1)[0]).toBe('runQuery');
expect(getNthMockFunctionCall(2)[0]).toBe('markQueryAsRun');
// We have a 4th call here, which returns null to terminate the loop early
expect(getNthMockFunctionCall(3)[0]).toBe('getNextQuery');
// Ensure there is no extra calls
expect(countMockFunctionCalls()).toBe(4);
});
function setGlobalMock(funcName, func) {
globalMocks[funcName] = func;
}
function getNthMockFunctionCall(n) {
return global.context.functions.execute.mock.calls[n];
}
function countMockFunctionCalls() {
return global.context.functions.execute.mock.calls.length;
}
});
There are two tests here, No queries need to be run
and One query needs to be run
. The error is in the second one, and I suspect that what is happening is that the mock recordings are persisting from the first test, and spoiling the results.
I have confirmed this by running this test on its own:
node node_modules/jest/bin/jest.js -t 'One query needs to be run'
However if I run both tests, like so:
node node_modules/jest/bin/jest.js functions/findAndRunQueries
Then I get a failure:
● Some tests for findAndRunQueries › One query needs to be run
expect(received).toBe(expected) // Object.is equality
Expected: "runQuery"
Received: "getNextQuery"
53 | // Ensure each func is called
54 | expect(getNthMockFunctionCall(0)[0]).toBe('getNextQuery');
> 55 | expect(getNthMockFunctionCall(1)[0]).toBe('runQuery');
| ^
56 | expect(getNthMockFunctionCall(2)[0]).toBe('markQueryAsRun');
57 |
58 | // We have a 4th call here, which returns null to terminate the loop early
at Object.test (functions/findAndRunQueries/findAndRunQueries.test.js:55:46)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 0.637s, estimated 1s
Ran all test suites matching /functions\/findAndRunQueries/i.
It says that the first parameter in the second call in the second test (getNthMockFunctionCall(1)[0]
) is getNextQuery
, when I am expecting runQuery
. I think this indicates that the mock results from the first test are persisting.
I have tried adding this:
beforeAll(() => {
jest.clearAllMocks();
});
I believe that "clearing" and "resetting" in Jest parlance are different - clearing just resets the counts, resetting removes mock implementations also. So it is clearing that I want, but unfortunately this makes no difference.
What further things can I try?
beforeAll
, and wondered: "Is that run for every test?":=)
– halfer