简体   繁体   English

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

[英]Cannot mock a module with jest, and test function calls

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.我写了一个我正在尝试测试的 React 组件。 The component is requiring another javascript file, exposing a function.该组件需要另一个 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 search was 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.mock work, 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.fn and test the arguments, I cannot make it work.无论我尝试访问jest.fn并测试参数,我都无法使其工作。

What am I doing wrong?我做错了什么?

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 SearchContainer after assigning a value to mockSearch and calling jest's mock , the specs point out that: Before instantiating a module, all of the modules it requested must be available .即使在您的原始代码中,您仅mockSearch分配值并调用 jest 的mock后才导入SearchContainer ,但规范指出: Before instantiating a module, all of the modules it requested must be available

Therefore, at the time SearchContainer is imported, and in turn imports search , your mockSearch variable is still undefined.因此,在导入SearchContainer并反过来导入search ,您的mockSearch变量仍未定义。

One might find this strange, as it would also seem to imply search.js isn't mocked yet, and so mocking wouldn't work at all.人们可能会觉得这很奇怪,因为它似乎也暗示search.js还没有被嘲笑,所以嘲笑根本不起作用。 Fortunately, (babel-)jest makes sure to hoist calls to mock and similar functions even higher than the imports, so that mocking will work.幸运的是,(babel-)jest 确保将调用提升到比导入更高的mock和类似函数,以便mock可以工作。

Nevertheless, the assignment of mockSearch , which is referenced by the mock's function, will not be hoisted with the mock call.尽管如此,由模拟函数引用的mockSearch的分配不会随着mock调用而提升。 So, the order of relevant operations will be something like:因此,相关操作的顺序将类似于:

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

When step 2 happens, the search function 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 mock call (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);
});

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

When it should have been an arrow function...当它应该是一个箭头函数时......

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

When mocking an api call with a response remember to async() => your test and await the wrapper update.当使用响应模拟 api 调用时,请记住 async() => 您的测试并等待包装器更新。 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...我的页面做了典型的 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);

    });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM