I'd like to change the implementation of a mocked dependency on a per single test basis by extending the default mock's behaviour and reverting it back to the original implementation when the following test executes.
More briefly this is what I'm trying to achieve:
- mock dependency
- change/extend mock implementation in a single test
- revert back to original mock when next test executes
I'm currently using Jest v21
.
Here is what a typical Jest test would look like:
__mocks__/myModule.js
const myMockedModule = jest.genMockFromModule('../myModule'); myMockedModule.a = jest.fn(() => true); myMockedModule.b = jest.fn(() => true); export default myMockedModule;
__tests__/myTest.js
import myMockedModule from '../myModule'; // Mock myModule jest.mock('../myModule'); beforeEach(() => { jest.clearAllMocks(); }); describe('MyTest', () => { it('should test with default mock', () => { myMockedModule.a(); // === true myMockedModule.b(); // === true }); it('should override myMockedModule.b mock result (and leave the other methods untouched)', () => { // Extend change mock myMockedModule.a(); // === true myMockedModule.b(); // === 'overridden' // Restore mock to original implementation with no side eeffects }); it('should revert back to default myMockedModule mock', () => { myMockedModule.a(); // === true myMockedModule.b(); // === true }); });
I tried a few strategies, but I didn't find any solution I could define satisfying.
1 - mockFn.mockImplementationOnce(fn)
Also used by
pros
- Reverts back to the original implementation after the first call
cons
- It will break if the test calls
b
more times - It won't revert to the original implementation until
b
is not called (leaking out in the next test)
code:
it('should override myModule.b mock result (and leave the other methods untouched)', () => { myMockedModule.b.mockImplementationOnce(() => 'overridden'); myModule.a(); // === true myModule.b(); // === 'overridden' });
2 - jest.doMock(moduleName, factory, options)
pros
- Explicitely re-mocks on every test
cons
- Cannot define a default mock implementation for all the tests
- Cannot extend a default implementation forcing to re-declare each mocked method
code:
it('should override myModule.b mock result (and leave the other methods untouched)', () => { jest.doMock('../myModule', () => { return { a: jest.fn(() => true, b: jest.fn(() => 'overridden', } }); myModule.a(); // === true myModule.b(); // === 'overridden' });
3 - Manual mocking with setter methods (as explained here)
pros
- Complete control over mocked results
cons
- Lot of boilerplate code
- Hard to mantain in long term
code:
__mocks__/myModule.js
const myMockedModule = jest.genMockFromModule('../myModule'); let a = true; let b = true; myMockedModule.a = jest.fn(() => a); myMockedModule.b = jest.fn(() => b); myMockedModule.__setA = (value) => { a = value }; myMockedModule.__setB = (value) => { b = value }; myMockedModule.__reset = () => { a = true; b = true; };
export default myMockedModule;
__tests__/myTest.js
it('should override myModule.b mock result (and leave the other methods untouched)', () => { myModule.__setB('overridden'); myModule.a(); // === true myModule.b(); // === 'overridden' myModule.__reset(); });
4 - jest.spyOn(object, methodName)
cons
- I can't revert back
mockImplementation
to the original mocked return value, affecting therefore the following tests
code:
beforeEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); }); // Mock myModule jest.mock('../myModule'); it('should override myModule.b mock result (and leave the other methods untouched)', () => { const spy = jest.spyOn(myMockedModule, 'b').mockImplementation(() => 'overridden'); myMockedModule.a(); // === true myMockedModule.b(); // === 'overridden' // How to get back to the original mocked value? });
Thank you in advance for any input/suggestion!
1 Answers
Answers 1
A nice pattern for writing test is to create a setup factory function that returns the data you need for testing the current module.
Below is some sample code following your second example although allows the provision of default and override values in a reusable way.
const spyReturns = returnValue => jest.fn(() => returnValue); describe("scenario", () => { const setup = (mockOverrides) => { const mockedFunctions = { a: spyReturns(true), b: spyReturns(true), ...mockOverrides } return { mockedModule: jest.doMock('../myModule', () => mockedFunctions); } } it("should return true for module a", () => { const {mockedModule} = setup(); expect(mockedModule.a()).toEqual(true) }); it("should return override for module a", () => { const EXPECTED_VALUE = "override" const {mockedModule} = setup({ a: spyReturns(EXPECTED_VALUE)}); expect(mockedModule.a()).toEqual(EXPECTED_VALUE) }) });
0 comments:
Post a Comment