I have a Jest test suite of integration tests using a suite-global mock for database access, where depending on the SQL I return different mock responses:
jest.mock('@my-org/our-mysql-wrapper', () => {
const query = jest.fn(async (sql, params) => {
if (sql === 'select foo from bar') {
return [];
} else if (sql === 'select baz') {
return [{ messageId: 3 }, { messageId: 4 }, { messageId: 5 }];
} else if (...) {
return ...;
} else {
console.log('UNEXPECTED QUERY SENT TO MOCK: ', sql, params);
return [];
}
});
const end = jest.fn(async () => true);
return jest.fn(() => ({ query, end }));
});
describe('suite', () => {
//tests here
});
This works great for positive tests, but where I'm getting frustrated with it is for negative tests. For example, in some cases if the DB were to return no results we might want to throw an error. In order to test that I need to have my mock behave differently for the same input. Whereas a typical positive test would not overwrite the db mock before running, the negative test needs to:
it('should throw and handle an error if the db returns no results for Widget lookup', async () => {
const mockDB = require('@my-org/our-mysql-wrapper')();
mockDB.query.mockImplementation(jest.fn(asnyc (sql, params) => {
if ( sql === 'select * from Widgets' ){
//this is the use-case that I want to override for this test
return [];
}else{
//...
}
}));
const someValue = await tool.doThing();
expect(buglogger).toHaveBeenCalled(); //actual test will be more specific...
//I tried plugging in mockRestore/mockClear/mockReset here
});
As written above this test actually passes, but it breaks tests that run after it because it doesn't clean up after itself. To the best of my understanding, this is what mockClear()
, mockReset()
, and mockRestore()
are supposed to do in different variations; but I haven't been able to find a way to restore my mock to the original pre-override mocked implementation at the end of my test.
I've also used jest.spyOn()
in some other cases, but that doesn't do what I'm after either. In this case, my test fails and the mock remains broken for other tests, too.
it('should throw and handle an error if the db returns no results for Widget lookup', async () => {
const mockDB = require('@my-org/our-mysql-wrapper')();
jest.spyOn(mockDb, 'query');
mockDB.query.mockImplementation(jest.fn(asnyc (sql, params) => {
if ( sql === 'select * from Widgets' ){
//this is the use-case that I want to override for this test
return [];
}else{
//...
}
}));
const someValue = await tool.doThing();
expect(buglogger).toHaveBeenCalled(); //actual test will be more specific...
mockDb.query.mockRestore();
});
I have also tried mockImplementationOnce()
and that won't work for me because the query in question is not the first one that will be run. Using this method does clean up after itself automatically, but doesn't (can't, as far as I can tell) make my test pass because it cleans itself up after the first use before the query in question is called.
BUT since mockImplementationOnce
can clean itself up in a way that restores the original mock, shouldn't there be some manual way to override an existing mock just for one test? That's what mockImplementationOnce
is doing, isn't it? (cleaning up after first call not after 1 test; but it appears to be restoring the original mock...)
What am I doing wrong, here?