Tarun
Tarun

Reputation: 1089

How to mock a variable in ES6 Module with jest such that the actual function runs with mocked value

Here is a sample code where I want to test the function buttonReducer. But the case names in the reducer functions are generated in another function. So, when I want to test my reducer alone, I want to override the RESET_TYPE, FETCH_TYPE, and SHOW_TYPE so that I am able to test all the scenarios.

//main reducer.js
import {getActionTypeName} from './functions';

export const RESET_TYPE = getActionTypeName('RESET');
export const FETCH_TYPE = getActionTypeName('RESET');
export const SHOW_TYPE = getActionTypeName('RESET');

const initialState = {
  name: 'John'
};

export const buttonReducer = (state = {...initialState}, action){
  switch(action.type){
    case RESET_TYPE: {
      const newState = {"some random change", ...initialState};
      return newState;
    }
    case FETCH_TYPE: {
      const newState = {"some random change", ...initialState};
      return newState;
    }
    case SHOW_TYPE: {
      const newState = {"some random change", ...initialState};
      return newState;
    }
    default: {
      return state;
    }
  }
}

Here are few things that I tried:

1.

jest.mock('./functions', ()=> {
    return {
        getActionTypeName: ()=>('return whatever I want')
    }
}

But this doesn't work.

  1. I am fully aware of running the entire app using redux-mock-store. But what I am specifically looking for, is to just override existing variables so that I can do scenario tests on a function

So essentially, I want to run the reducer by changing the values of the variables and not merely mock them just for the test case.

Upvotes: 0

Views: 8291

Answers (2)

Zachary Johnson
Zachary Johnson

Reputation: 67

You can have your mock return a jest fn that you can later change the return value for. That way you don't have to do a new mock each time.

const getActionTypeName = jest.fn(() => {});

jest.mock('./pathToModule', () => {
  return {
    __esModule: true,
    getActionTypeName: () => getActionTypeName(),
  }
})

describe('functionBeingTested', () => {
  it('should do something', () => {
    getActionTypeName.mockReturnValue('RUNTIME_RESET_TYPE');
    // test code...
    // assertions...
  });
});

Upvotes: 1

Lin Du
Lin Du

Reputation: 102237

Since the action types RESET_TYPE, FETCH_TYPE, and SHOW_TYPE are defined and calculated in module scope at runtime, in order to eliminate the caching effect of the module import, jest.resetModules() method is required. Before executing the test case, we need to call it. So that we can get a fresh module with fresh module variables. This prevents test cases from influencing each other.

Now, we can use jest.doMock(moduleName, factory, options) and mockFn.mockReturnValueOnce(value) methods to mock ./functions module and getActionTypeName function with different returned value for each test case.

E.g.

reducer.js:

import { getActionTypeName } from './functions';

export const RESET_TYPE = getActionTypeName('RESET');
export const FETCH_TYPE = getActionTypeName('RESET');
export const SHOW_TYPE = getActionTypeName('RESET');

const initialState = {
  name: 'John',
};

export const buttonReducer = (state = { ...initialState }, action) => {
  switch (action.type) {
    case RESET_TYPE: {
      const newState = { a: 'a', ...initialState };
      return newState;
    }
    case FETCH_TYPE: {
      const newState = { b: 'b', ...initialState };
      return newState;
    }
    case SHOW_TYPE: {
      const newState = { c: 'c', ...initialState };
      return newState;
    }
    default: {
      return state;
    }
  }
};

functions.js:

export function getActionTypeName() {
  console.log('real implementation');
}

reducer.test.js:

describe('68179950', () => {
  beforeEach(() => {
    jest.resetModules();
  });
  it('should return dynamic RESET_TYPE', () => {
    jest.doMock('./functions', () => ({
      getActionTypeName: jest.fn().mockReturnValueOnce('RUNTIME_RESET_TYPE'),
    }));
    const { buttonReducer } = require('./reducer');
    const actual = buttonReducer({}, { type: 'RUNTIME_RESET_TYPE' });
    expect(actual).toEqual({ name: 'John', a: 'a' });
  });

  it('should return dynamic FETCH_TYPE', () => {
    jest.doMock('./functions', () => ({
      getActionTypeName: jest.fn().mockReturnValueOnce('RUNTIME_RESET_TYPE').mockReturnValueOnce('RUNTIME_FETCH_TYPE'),
    }));
    const { buttonReducer } = require('./reducer');
    const actual = buttonReducer({}, { type: 'RUNTIME_FETCH_TYPE' });
    expect(actual).toEqual({ name: 'John', b: 'b' });
  });

  it('should return dynamic SHOW_TYPE', () => {
    jest.doMock('./functions', () => ({
      getActionTypeName: jest
        .fn()
        .mockReturnValueOnce('RUNTIME_RESET_TYPE')
        .mockReturnValueOnce('RUNTIME_FETCH_TYPE')
        .mockReturnValueOnce('RUNTIME_SHOW_TYPE'),
    }));
    const { buttonReducer } = require('./reducer');
    const actual = buttonReducer({}, { type: 'RUNTIME_SHOW_TYPE' });
    expect(actual).toEqual({ name: 'John', c: 'c' });
  });
});

unit test result:

 PASS  examples/68179950/reducer.test.js (18.013 s)
  68179950
    ✓ should return dynamic RESET_TYPE (12582 ms)
    ✓ should return dynamic FETCH_TYPE (1 ms)
    ✓ should return dynamic SHOW_TYPE (7 ms)

------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------|---------|----------|---------|---------|-------------------
All files   |   93.33 |       60 |     100 |   92.86 |                   
 reducer.js |   93.33 |       60 |     100 |   92.86 | 26                
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        22.095 s

Upvotes: 1

Related Questions