如何用jest模拟ES6模块中的变量,使实际函数使用模拟值运行



这里有一个示例代码,我想在其中测试函数buttonReducer。但是reducer函数中的事例名称是在另一个函数中生成的。因此,当我想单独测试减速器时,我想覆盖RESET_TYPE、FETCH_TYPE和SHOW_TYPE,以便能够测试所有场景。

//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;
}
}
}

以下是我尝试过的几件事:

1.

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

但这行不通。

  1. 我完全知道使用redux模拟存储运行整个应用程序。但我特别想要的是覆盖现有的变量,这样我就可以对函数进行场景测试

因此,本质上,我想通过更改变量的值来运行reducer,而不仅仅是为测试用例模拟它们。

由于操作类型RESET_TYPEFETCH_TYPESHOW_TYPE是在运行时在模块范围内定义和计算的,为了消除模块import的缓存效应,需要jest.resetModules((方法。在执行测试用例之前,我们需要调用它。这样我们就可以获得一个带有新鲜模块变量的新鲜模块。这样可以防止测试用例相互影响。

现在,我们可以使用jest.doMock(moduleName,factory,options(和mockFn.mockReturnValueOnce(value(方法来模拟每个测试用例具有不同返回值的./functions模块和getActionTypeName函数。

例如

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' });
});
});

单元测试结果:

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

您可以让mock返回一个jest-fn,稍后可以更改其返回值。这样你就不必每次都做一个新的模拟了。

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...
});
});

最新更新