无法使用jest模拟模块,并测试函数调用

ghu*_*sse 27 javascript unit-testing mocking reactjs jestjs

我使用create-app-component创建了一个项目,它使用构建脚本(babel,webpack,jest)配置新的应用程序.

我写了一个我正在尝试测试的React组件.该组件需要另一个javascript文件,公开一个函数.

我的search.js文件

export {
  search,
}

function search(){
  // does things
  return Promise.resolve('foo')
}
Run Code Online (Sandbox Code Playgroud)

我的反应成分:

import React from 'react'
import { search } from './search.js'
import SearchResults from './SearchResults'

export default SearchContainer {
  constructor(){
    this.state = {
      query: "hello world"
    }
  }

  componentDidMount(){
    search(this.state.query)
      .then(result => { this.setState({ result, })})
  }

  render() {
    return <SearchResults 
            result={this.state.result}
            />
  }
}
Run Code Online (Sandbox Code Playgroud)

在我的单元测试中,我想检查是否search使用正确的参数调用了该方法.

我的测试看起来像这样:

import React from 'react';
import { shallow } from 'enzyme';
import should from 'should/as-function';

import SearchResults from './SearchResults';

let mockPromise;

jest.mock('./search.js', () => {
  return { search: jest.fn(() => mockPromise)};
});

import SearchContainer from './SearchContainer';

describe('<SearchContainer />', () => {
  it('should call the search module', () => {
    const result = { foo: 'bar' }
    mockPromise = Promise.resolve(result);
    const wrapper = shallow(<SearchContainer />);

    wrapper.instance().componentDidMount();

    mockPromise.then(() => {
      const searchResults = wrapper.find(SearchResults).first();
      should(searchResults.prop('result')).equal(result);
    })    
  })
});
Run Code Online (Sandbox Code Playgroud)

我已经很难弄清楚如何jest.mock工作,因为它需要变量作为前缀mock.

但是如果我想测试方法的参数search,我需要在我的测试中使模拟函数可用.

如果我转换模拟部分,使用变量:

const mockSearch = jest.fn(() => mockPromise)
jest.mock('./search.js', () => {
  return { search: mockSearch};
});
Run Code Online (Sandbox Code Playgroud)

我收到此错误:

TypeError:(0,_search.search)不是函数

无论我试图访问jest.fn和测试参数,我都无法使它工作.

我究竟做错了什么?

Tom*_*mty 46

问题

您收到该错误的原因与如何提升各种操作有关.

即使在您的原始代码中,您只需SearchContainer mockSearchjest 指定值并调用jest 之后导入mock,但规范指出:Before instantiating a module, all of the modules it requested must be available.

因此,在SearchContainer导入时,依次导入search,您的mockSearch变量仍未定义.

人们可能会发现这很奇怪,因为它似乎也暗示着search.js没有被嘲笑,所以嘲笑根本不起作用.幸运的是,(babel-)jest确保提升调用mock和类似功能甚至高于导入,因此模拟将起作用.

尽管如此,mockSearch模拟函数引用的赋值将不会随着mock调用而提升.因此,相关操作的顺序将是这样的:

  1. 设置一个模拟工厂 ./search.js
  2. 导入所有依赖项,这将调用模拟工厂以获取组件的函数
  3. 为...分配值 mockSearch

当第2步发生时,search传递给组件的函数将是未定义的,并且步骤3中的赋值为时已晚,无法更改.

如果您将模拟函数创建为mock调用的一部分(这样它也将被提升),它将在组件模块导入时具有有效值,如您的早期示例所示.

正如您所指出的,当您想在测试中使模拟函数可用时,问题就开始了.有一个明显的解决方案:单独导入您已经模拟过的模块.

既然你现在知道开玩笑实际上是在进口之前发生的,那么一个简单的方法就是:

import { search } from './search.js'; // This will actually be the mock

jest.mock('./search.js', () => {
  return { search: jest.fn(() => mockPromise) };
});

[...]

beforeEach(() => {
  search.mockClear();
});

it('should call the search module', () => {
  [...]

  expect(search.mock.calls.length).toBe(1);
  expect(search.mock.calls[0]).toEqual(expectedArgs);
});
Run Code Online (Sandbox Code Playgroud)

实际上,您可能想要替换:

import { search } from './search.js';
Run Code Online (Sandbox Code Playgroud)

附:

const { search } = require.requireMock('./search.js');
Run Code Online (Sandbox Code Playgroud)

这应该不会产生任何功能差异,但可能使你正在做的事情更明确(并且应该帮助任何人使用类型检查系统,如Flow,所以它不认为你试图调用模拟函数在原件上search).

附加说明

如果您需要模拟的是模块本身的默认导出,那么所有这些都是绝对必要的.否则(正如@publicJorn指出的那样),你可以简单地在测试中重新分配特定的相关成员,如下所示:

import * as search from './search.js';

beforeEach(() => {
  search.search = jest.fn(() => mockPromise);
});
Run Code Online (Sandbox Code Playgroud)

  • @Tomty 至于你的补充说明:你实际上只需要更改你的测试导入: `import * as search from './search.js'` 然后 `search.search = jest.fn(() =&gt; mockPromise)` 。这样你就可以保持你的实际代码干净整洁“import { search } from './search.js'”。这是因为您必须模拟该函数,但不能直接模拟导入的常量。 (2认同)