如何在玩笑中模拟导出的常量

我有一个依赖于导出 const变量的文件。这个变量被设置为 true,但是如果需要的话,可以手动设置为 false,以防止下游服务请求它时出现某些行为。

我不确定如何在 Jest 中模拟 const变量,以便更改它的值来测试 truefalse条件。

例如:

//constants module
export const ENABLED = true;


//allowThrough module
import { ENABLED } from './constants';


export function allowThrough(data) {
return (data && ENABLED === true)
}


// jest test
import { allowThrough } from './allowThrough';
import { ENABLED } from './constants';


describe('allowThrough', () => {
test('success', () => {
expect(ENABLED).toBE(true);
expect(allowThrough({value: 1})).toBe(true);
});


test('fail, ENABLED === false', () => {
//how do I override the value of ENABLED here?


expect(ENABLED).toBe(false) // won't work because enabled is a const
expect(allowThrough({value: 1})).toBe(true); //fails because ENABLED is still true
});
});
165458 次浏览

如果您将 ES6模块语法编译成 ES5,则此示例将工作,因为最终,所有模块导出都属于同一个对象,可以对其进行修改。

import { allowThrough } from './allowThrough';
import { ENABLED } from './constants';
import * as constants from './constants';


describe('allowThrough', () => {
test('success', () => {
constants.ENABLED = true;


expect(ENABLED).toBe(true);
expect(allowThrough({ value: 1 })).toBe(true);
});


test('fail, ENABLED === false', () => {
constants.ENABLED = false;


expect(ENABLED).toBe(false);
expect(allowThrough({ value: 1 })).toBe(false);
});
});

或者,您可以切换到原始的 commomjs require函数,并在 jest.mock(...)的帮助下这样做:

const mockTrue = { ENABLED: true };
const mockFalse = { ENABLED: false };


describe('allowThrough', () => {
beforeEach(() => {
jest.resetModules();
});


test('success', () => {
jest.mock('./constants', () => mockTrue)
const { ENABLED } = require('./constants');
const { allowThrough } = require('./allowThrough');


expect(ENABLED).toBe(true);
expect(allowThrough({ value: 1 })).toBe(true);
});


test('fail, ENABLED === false', () => {
jest.mock('./constants', () => mockFalse)
const { ENABLED } = require('./constants');
const { allowThrough } = require('./allowThrough');


expect(ENABLED).toBe(false);
expect(allowThrough({ value: 1 })).toBe(false);
});
});

多亏了 getters 和 spyOn,ES6 + 和 jest 22.1.0 + 还有另一种方法可以做到这一点。

默认情况下,不能监视布尔值或 number 等基本类型。不过,您可以使用自己的 mock 替换导入的文件。Getter 方法仍然像原始成员一样工作,但允许我们监视它。有一个间谍在我们的目标成员,你基本上可以做它任何你想要的,就像一个 jest.fn()模拟。

下面是一个例子

// foo.js
export const foo = true; // could be expression as well
// subject.js
import { foo } from './foo'


export default () => foo
// subject.spec.js
import subject from './subject'


jest.mock('./foo', () => ({
get foo () {
return true // set some default value
}
}))


describe('subject', () => {
const mySpy = jest.spyOn(subject.default, 'foo', 'get')


it('foo returns true', () => {
expect(subject.foo).toBe(true)
})


it('foo returns false', () => {
mySpy.mockReturnValueOnce(false)
expect(subject.foo).toBe(false)
})
})


文件里有更多信息。

感谢@Luke,我能够扩展他对我需求的回答,我有以下要求:

  • 只模仿文件中的某些值-不是全部
  • 只在单个测试中运行模拟。

原来 doMock()就像 mock()但是不会被吊起来。此外,requireActual()还可以用来获取原始数据。

我的 config.js文件-我只需要模拟它的一部分

export const SOMETHING = 'blah'
export const OTHER = 'meh'

我的测试文件

// import { someFunc } from  'some/file' // This won't work with doMock - see below
describe('My test', () => {


test('someFunc() does stuff', async () => {


// Here I mock the config file which gets imported somewhere deep in my code
jest.doMock('config.js', () => {


// Grab original
const originalModule = jest.requireActual('config')


// Return original but override some values
return {
__esModule: true, // Depends on your setup
...originalModule,
SOMETHING: 'boom!'
}
})


// Because `doMock` doesn't get hoisted we need to import the function after
const { someFunc } = await import(
'some/file'
)


// Now someFunc will use the original config values but overridden with SOMETHING=boom!
const res = await someFunc()
})
})

根据其他测试,您可能还需要在某些地方使用 resetModules(),例如 beforeAllafterAll

文件:

你也可以使用 “ Object.defeProperty”来重新定义你的属性,而不是 Jest 或者在吊装方面遇到麻烦等等

它可以很容易地为每个测试用例重新定义。

这是一个基于我的一些文件的伪代码示例:

从本地化文件:

export const locale = 'en-US';

在另一个文件中,我们使用了 locale:

import { locale } from 'src/common/localization';
import { format } from 'someDateLibrary';


// 'MMM' will be formatted based on locale
const dateFormat = 'dd-MMM-yyyy';


export const formatDate = (date: Number) => format(date, dateFormat, locale)


如何在测试文件中调试

import * as Localization from 'src/common/localization';
import { formatDate } from 'src/utils/dateUtils';


describe('format date', () => {
test('should be in Danish format', () => {
Object.defineProperty(Localization, 'locale', {
value: 'da-DK'
});
expect(formatDate(1589500800000)).toEqual('15-maj-2020');
});
test('should be in US format', () => {
Object.defineProperty(Localization, 'locale', {
value: 'en-US'
});
expect(formatDate(1589500800000)).toEqual('15-May-2020');
});
});

我通过在 reducers 中初始化 ContstantsFile.js 中的常量来解决这个问题。然后把它放在复原商店里。由于 jest.mock 无法模仿 contstantsFile.js

constantsFile.js
-----------------
const MY_CONSTANTS = {
MY_CONSTANT1: "TEST",
MY_CONSTANT2: "BEST",
};
export defualt MY_CONSTANTS;


reducers/index.js
-----------------
import MY_CONST from "./constantsFile";


const initialState = {
...MY_CONST
}
export const AbcReducer = (state = initialState, action) => {.....}


ABC.jsx
------------
import { useSelector } from 'react-redux';
const ABC = () => {
const const1 = useSelector(state) => state. AbcReducer. MY_CONSTANT1:
const const2 = useSelector(state) => state. AbcReducer. MY_CONSTANT2:
.......

现在我们可以轻松地在 test.jsx 中模拟存储,并为常量提供所需的值。

Abc.text.jsx
-------------
import thunk from 'redux-thunk';
import configureMockStore from 'redux-mock-store';


describe('Abc mock constants in jest', () => {
const mockStore = configureMockStore([thunk]);
let store = mockStore({
AbcReducer: {
MY_CONSTANT1 ="MOCKTEST",
MY_CONSTANT2 = "MOCKBEST",
}
});


test('your test here', () => { .....

现在,当测试运行时,它总是从 mock store 中选择常量值。

面对同样的问题,我发现这篇博客文章非常有用,而且比@CyberWombat 用例简单得多:

Https://remarkablemark.org/blog/2018/06/28/jest-mock-default-named-export/

// esModule.js
export default 'defaultExport';
export const namedExport = () => {};
// esModule.test.js
jest.mock('./esModule', () => ({
__esModule: true, // this property makes it work
default: 'mockedDefaultExport',
namedExport: jest.fn(),
}));


import defaultExport, { namedExport } from './esModule';
defaultExport; // 'mockedDefaultExport'
namedExport; // mock function

不幸的是,没有一个解决方案对我有效,或者更准确地说,有些解决方案确实有效,但是抛出了 linting、 TypeScript 或编译错误,所以我将发布我的解决方案,它们都对我有效,并且符合当前的编码标准:

// constants.ts
// configuration file with defined constant(s)
export const someConstantValue = true;
// module.ts
// this module uses the defined constants
import { someConstantValue } from './constants';


export const someCheck = () => someConstantValue ? 'true' : 'false';
// module.test.ts
// this is the test file for module.ts
import { someCheck } from './module';


// Jest specifies that the variable must start with `mock`
const mockSomeConstantValueGetter = jest.fn();
jest.mock('./constants', () => ({
get someConstantValue() {
return mockSomeConstantValueGetter();
},
}));


describe('someCheck', () => {
it('returns "true" if someConstantValue is true', () => {
mockSomeConstantValueGetter.mockReturnValue(true);
expect(someCheck()).toEqual('true');
});


it('returns "false" if someConstantValue is false', () => {
mockSomeConstantValueGetter.mockReturnValue(false);
expect(someCheck()).toEqual('false');
});
});

我需要的最常见场景是模拟一个类使用的常量(在我的例子中是一个 React 组件,但它可以是任何 ES6类)。

@ Luke 的回答对此很有效,我只花了一分钟就想通了,所以我想我应该换一个更明确的例子。

关键是您的常量需要在一个单独的 import文件中,这样 import本身就可以被 jest截断/模拟。

下面这些对我来说很有用。

首先,定义你的常量:

// src/my-component/constants.js


const MY_CONSTANT = 100;


export { MY_CONSTANT };

接下来,我们有一个实际使用常量的类:

// src/my-component/index.jsx


import { MY_CONSTANT } from './constants';


// This could be any class (e.g. a React component)
class MyComponent {
constructor() {
// Use the constant inside this class
this.secret = MY_CONSTANT;
console.log(`Current value is ${this.secret}`);
}
}


export default MyComponent

最后,我们还有测试,这里有两个用例需要处理:

  1. 模拟此文件中所有测试的 MY_CONSTANT生成值
  2. 允许特定测试进一步覆盖该测试的 MY_CONSTANT

第一部分是通过在测试文件的顶部使用 jest.mock实现的。

第二种是通过使用 jest.spyOn进一步监视导出的常量列表来实现的。就像是一个笑话加上一个笑话。

// test/components/my-component/index.js


import MyComponent from 'src/my-component';
import allConstants from 'src/my-component/constants';


jest.mock('src/my-component/constants', () => ({
get MY_CONSTANT () {
return 30;
}
}));


it('mocks the value of MY_CONSTANT', () => {
// Initialize the component, or in the case of React, render the component
new MyComponent();


// The above should cause the `console.log` line to print out the
// new mocked value of 30
});


it('mocks the value of MY_CONSTANT for this test,', () => {
// Set up the spy. You can then use any jest mocking method
// (e.g. `mockReturnValue()`) on it
const mySpy = jest.spyOn(allConstants, 'MY_CONSTANT', 'get')
mySpy.mockReturnValue(15);


new MyComponent();


// The above should cause the `console.log` line to print out the
// new mocked value of 15
});

因为我们不能直接覆盖/模拟该值

// foo.js
export const foo = true; // could be expression as well


// spec file
import * as constants from './foo'


Object.defineProperty(constant, 'foo', {value: 1})

职能:

Object.defineProperty(store, 'doOneThing', {value: jest.fn()})

模拟变量的方法之一是以下解决方案:

例如,存在带有常量的 ./constants.js文件:

export const CONSTATN_1 = 'value 1';
export const CONSTATN_2 = 'value 2';

还有一个测试 ./file-with-tests.spec.js文件,您需要在其中执行模拟变量。 如果需要模拟多个变量,则需要使用 jest.requireActual来使用其余变量的实际值。

jest.mock('./constants', () => ({
...jest.requireActual('./constants'),
CONSTATN_1: 'mock value 1',
}));

如果需要模拟使用 jest.requireActual的所有变量是可选的。

jest.mock('./constants', () => ({
CONSTATN_1: 'mock value 1',
CONSTATN_2: 'mock value 2'
}));

对我来说,最简单的解决方案是重新定义导入的 object 属性,如下所述:

Https://flutterq.com/how-to-mock-an-exported-const-in-jest/

// foo.js
export const foo = true; // could be expression as well


// spec file
import * as constants from './foo'


Object.defineProperty(constant, 'foo', {value: 1, writable: true})

在类型脚本中,不能覆盖常量值,但是可以覆盖常量值的 getter 函数。

const mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter = jest.fn();
jest.mock('lib/constants', () => ({
...jest.requireActual('lib/constants'),
get NEXT_PUBLIC_ENABLE_HCAPTCHA() {
return mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter();
},
}));

并在测试中使用作为

      beforeEach(() => {
mockNEXT_PUBLIC_ENABLE_HCAPTCHAGetter.mockReturnValue('true');
});

谢谢你们的回答。

对我来说,这比这里的所有建议都要简单得多

// foo.ts
export const foo = { bar: "baz" };
// use-foo.ts
// this is just here for the example to have a function that consumes foo
import { foo } from "./foo";


export const getFoo = () => foo;
// foo.spec.ts
import "jest";
import { foo } from "./foo";
import { getFoo } from "./use-foo";


test("foo.bar should be 'other value'", () => {
const mockedFoo = foo as jest.Mocked<foo>;
mockedFoo.bar = "other value";


const { bar } = getFoo();
expect(bar).toBe("other value"); // success
expect(bar).toBe("baz"); // fail
};

希望这对谁有帮助。