Javascript 不能用玩笑模拟模块,并测试函数调用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39633919/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 22:45:52  来源:igfitidea点击:

Cannot mock a module with jest, and test function calls

javascriptunit-testingreactjsmockingjestjs

提问by ghusse

I create a project using create-app-component, which configures a new app with build scripts (babel, webpack, jest).

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

I wrote a React component that I'm trying to test. The component is requiring another javascript file, exposing a function.

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

My search.js file

我的 search.js 文件

export {
  search,
}

function search(){
  // does things
  return Promise.resolve('foo')
}

My react component:

我的反应组件:

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

In my unit tests, I want to check that the method searchwas called with the correct arguments.

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

My tests look something like that:

我的测试看起来像这样:

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

I already had a hard time to figure out how to make jest.mockwork, because it requires variables to be prefixed by mock.

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

But if I want to test arguments to the method search, I need to make the mocked function available in my tests.

但是如果我想测试方法的参数search,我需要在我的测试中提供模拟函数。

If I transform the mocking part, to use a variable:

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

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

I get this error:

我收到此错误:

TypeError: (0 , _search.search) is not a function

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

Whatever I try to have access to the jest.fnand test the arguments, I cannot make it work.

无论我尝试访问jest.fn和测试参数,我都无法使其工作。

What am I doing wrong?

我究竟做错了什么?

回答by Tomty

The problem

问题

The reason you're getting that error has to do with how various operations are hoisted.

您收到该错误的原因与各种操作的提升方式有关。

Even though in your original code you only import SearchContainerafterassigning a value to mockSearchand calling jest's mock, the specspoint out that: Before instantiating a module, all of the modules it requested must be available.

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

Therefore, at the time SearchContaineris imported, and in turn imports search, your mockSearchvariable is still undefined.

因此,在SearchContainer导入时,然后导入search,您的mockSearch变量仍然未定义。

One might find this strange, as it would also seem to imply search.jsisn't mocked yet, and so mocking wouldn't work at all. Fortunately, (babel-)jest makes sure to hoist calls to mockand similar functions even higherthan the imports, so that mocking will work.

人们可能会觉得这很奇怪,因为它似乎也暗示search.js尚未被嘲笑,因此嘲笑根本不起作用。幸运的是,(babel-)jest 确保将调用mock和类似函数的调用提升到甚至高于导入的级别,以便模拟可以工作。

Nevertheless, the assignment of mockSearch, which is referenced by the mock's function, will not be hoisted with the mockcall. So, the order of relevant operations will be something like:

尽管如此,mockSearch由模拟函数引用的 的赋值不会随着mock调用而提升。因此,相关操作的顺序将类似于:

  1. Set a mock factory for ./search.js
  2. Import all dependencies, which will call the mock factory for a function to give the component
  3. Assign a value to mockSearch
  1. 设置一个模拟工厂 ./search.js
  2. 导入所有依赖项,这将调用模拟工厂的函数来提供组件
  3. 赋值给 mockSearch

When step 2 happens, the searchfunction passed to the component will be undefined, and the assignment at step 3 is too late to change that.

当第 2 步发生时,search传递给组件的函数将是未定义的,第 3 步的赋值来不及改变它。

Solution

解决方案

If you create the mock function as part of the mockcall (such that it'll be hoisted too), it'll have a valid value when it's imported by the component module, as your early example shows.

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

As you pointed out, the problem begins when you want to make the mocked function available in your tests. There is one obvious solution to this: separately import the module you've already mocked.

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

Since you now know jest mocking actually happens before imports, a trivial approach would be:

由于您现在知道开玩笑实际上发生在导入之前,因此一种简单的方法是:

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

In fact, you might want to replace:

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

import { search } from './search.js';

With:

和:

const { search } = require.requireMock('./search.js');

This shouldn't make any functional difference, but might make what you're doing a bit more explicit (and should help anyone using a type-checking system such as Flow, so it doesn't think you're trying to call mock functions on the original search).

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

Additional note

附加说明

All of this is only strictly necessary if what you need to mock is the default export of a module itself. Otherwise (as @publicJorn points out), you can simply re-assign the specific relevant member in the tests, like so:

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

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

beforeEach(() => {
  search.search = jest.fn(() => mockPromise);
});

回答by Nabil Freeman

In my case, I got this error because I failed to implement the mock correctly.

就我而言,我收到此错误是因为我未能正确实现模拟。

My failing code:

我的失败代码:

jest.mock('react-native-some-module', mockedModule);

jest.mock('react-native-some-module', mockedModule);

When it should have been an arrow function...

当它应该是一个箭头函数时......

jest.mock('react-native-some-module', () => mockedModule);

jest.mock('react-native-some-module', () => mockedModule);

回答by swandog

When mocking an api call with a response remember to async() => your test and await the wrapper update. My page did the typical componentDidMount => make API call => positive response set some state...however the state in the wrapper did not get updated...async and await fix that...this example is for brevity...

当使用响应模拟 api 调用时,请记住 async() => 您的测试并等待包装器更新。我的页面做了典型的 componentDidMount => 进行 API 调用 => 积极响应设置了一些状态......但是包装器中的状态没有得到更新......异步并等待修复......这个例子是为了简洁......

...otherImports etc...
const SomeApi = require.requireMock('../../api/someApi.js');

jest.mock('../../api/someApi.js', () => {
    return {
        GetSomeData: jest.fn()
    };
});

beforeEach(() => {
    // Clear any calls to the mocks.
    SomeApi.GetSomeData.mockClear();
});

it("renders valid token", async () => {
        const responseValue = {data:{ 
            tokenIsValid:true}};
        SomeApi.GetSomeData.mockResolvedValue(responseValue);
        let wrapper = shallow(<MyPage {...props} />);
        expect(wrapper).not.toBeNull();
        await wrapper.update();
        const state = wrapper.instance().state;
        expect(state.tokenIsValid).toBe(true);

    });