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

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

问题描述

我使用 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 ,然后为 mockSearch 分配值并调用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. 导入所有依赖项,这将调用模拟工厂以提供给组件提供功能的功能
  4. 将值分配给 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天全站免登陆