【问题标题】:Cannot mock a module with jest, and test function calls不能用玩笑模拟模块,并测试函数调用
【发布时间】:2017-01-30 17:29:55
【问题描述】:

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

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

我的 search.js 文件

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 是否使用正确的参数调用。

我的测试看起来像这样:

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 为前缀的变量。

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

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

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

我收到此错误:

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

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

我做错了什么?

【问题讨论】:

    标签: javascript unit-testing reactjs mocking jestjs


    【解决方案1】:

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

    我的失败代码:

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

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

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

    【讨论】:

      【解决方案2】:

      当使用响应模拟 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);
      
          });
      

      【讨论】:

      • 另一个注意事项...在测试否定响应时,请确保您的页面/组件 api 调用使用 myapi.getsomedata().then(success=>{},error =>{});语法不是 .then(success=>{}).catch(function(err){....
      【解决方案3】:

      问题

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

      即使在您的原始代码中您只导入 SearchContainermockSearch 赋值并调用 jest 的 mockspecs 指出: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);
      });
      

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

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

      与:

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

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

      补充说明

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

      import * as search from './search.js';
      
      beforeEach(() => {
        search.search = jest.fn(() => mockPromise);
      });
      

      【讨论】:

      • 感谢您提供如此详细的回复。我一定会尝试你的解决方案。
      • 虽然之前的解决方案在技术上应该可行(我什至最终弄清楚了为什么需要 var),但我现在已经编辑了答案,使用了一个不那么丑陋的答案,IMO。跨度>
      • 感谢您的帮助。使用第二种解决方案,我收到错误cannot set property search of search。第一个解决方案有效:我需要导入 lib 并调用 jest 一次(而不是在 beforeEach 触发器中)。
      • @Tomty 至于你的附加说明:你实际上只需要更改你的测试导入:import * as search from './search.js' 然后search.search = jest.fn(() =&gt; mockPromise)。这样你就可以让你的实际代码保持整洁import { search } from './search.js'。这是因为您必须模拟函数,但不能直接模拟导入的常量。
      • @publicJorn 好点!我将相应地更新我的答案的“附加说明”部分。或者,如果您想将此作为单独的答案发布,请告诉我,我会将其从我的答案中删除。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-06-03
      • 1970-01-01
      • 2021-06-16
      • 2021-01-24
      • 2020-01-31
      • 2021-12-09
      • 1970-01-01
      相关资源
      最近更新 更多