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

IT技术 javascript reactjs unit-testing jestjs
2021-04-10 23:19:03

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

我不确定如何const在 Jest 中模拟变量,以便我可以更改它的值以测试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
  });
});
6个回答

如果您将 ES6 module语法编译为 ES5,则此示例将起作用,因为最终所有module导出都属于同一个对象,可以修改。

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

或者,您可以切换到原始 commonjsrequire函数,并在以下帮助下执行此操作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);
    });
});
有没有办法同时导入import { ENABLED } from './constants'; 从 './constants' 导入 * 作为常量;在一行?我试过import * as constants, { ENABLED } from './constants'; 但抛出语法错误
2021-06-01 23:19:03
我收到这条消息The module factory of jest.mock() is not allowed to reference any out-of-scope variables.你遇到过这种情况吗?
2021-06-03 23:19:03
第一个例子是改变一个不允许的常量?它的工作原理是 * 作为将所有内容包装在对象中的常量,但如果您使用 flowtype,这是一个错误。
2021-06-09 23:19:03
第一个片段让我TS2540: Cannot assign to '<variable>' because it is a read-only property.出错
2021-06-13 23:19:03
仅当您使用require(your_module). import {...} from 'your_module'在测试中不起作用。
2021-06-14 23:19:03

由于 getter 和 spyOn,还有另一种方法可以在 ES6+ 和 jest 22.1.0+ 中做到这一点。

默认情况下,您不能窥探布尔或数字等原始类型。您可以使用自己的模拟替换导入的文件。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)
  })
})

在文档中阅读更多内容。

似乎它应该工作,但我无法让它工作。使用doMock对我有用- 请参阅jestjs.io/docs/en/...
2021-06-18 23:19:03
npmjs.com/package/jest-mock-primitive使用 getter 来完成类似的事情 /self-promotion。
2021-06-19 23:19:03

不幸的是,没有一个已发布的解决方案对我有用,或者更准确地说,有些解决方案确实有效,但抛出了 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';

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');
  });
});
这给了我 ReferenceError: Cannot access 'mockSomeConstantValueGetter' before initialization我正在使用 CRA。是否需要额外的配置来禁用提升?
2021-06-02 23:19:03
这给了我一个错误:“jest.mock()不允许的module工厂引用任何范围外的变量。”
2021-06-04 23:19:03
@EvHaus 变量名必须以mock示例开头const mockSomeConstantValueGetter = jest.fn();
2021-06-05 23:19:03
@IvanWang 这意味着您尚未定义变量 mockSomeConstantValueGetter
2021-06-09 23:19:03

感谢@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()诸如beforeAll或 之类的地方afterAll

文档:

看起来其他引用这个答案的答案掩盖了这样一个事实,即通过这个答案,模拟是有范围的,这就是 OP 最终所追求的。
2021-06-15 23:19:03

面对同样的问题,我发现这篇博文非常有用,而且比@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