Saturday, February 24, 2018

How to change mock implementation on a per single test basis [Jestjs]

Leave a Comment

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:

  1. mock dependency
  2. change/extend mock implementation in a single test
  3. 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)   }) }); 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment