不能用玩笑模拟模块,并测试函数调用 [英] Cannot mock a module with jest, and test function calls

查看:22
本文介绍了不能用玩笑模拟模块,并测试函数调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

我的 search.js 文件

My search.js file

export {
  search,
}

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

我的反应组件:

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

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

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

我的测试看起来像这样:

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

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

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

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

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

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

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

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

我收到此错误:

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

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

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

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

我做错了什么?

推荐答案

问题

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

The problem

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

即使在您的原始代码中,您只导入 SearchContainer aftermockSearch 赋值并调用 jest 的 mock>,规范指出:在实例化一个模块之前,所有的它请求的模块必须可用.

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.

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

Therefore, at the time SearchContainer is imported, and in turn imports search , your mockSearch variable is still undefined.

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

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. Fortunately, (babel-)jest makes sure to hoist calls to mock and similar functions even higher than the imports, so that mocking will work.

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

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

  1. ./search.js 设置一个模拟工厂
  2. 导入所有依赖项,这将调用模拟工厂的函数来赋予组件
  3. mockSearch
  4. 赋值
  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

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

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.

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

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.

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

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

与:

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

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

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

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

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:

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

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

这篇关于不能用玩笑模拟模块,并测试函数调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆