无法使用jest模拟模块,并测试函数调用

时间:2016-09-22 08:27:31

标签: javascript unit-testing reactjs mocking jestjs

我使用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并测试参数,我都无法使其发挥作用。

我做错了什么?

3 个答案:

答案 0 :(得分:40)

问题

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

即使在原始代码中,您只需在为SearchContainer分配值并调用jest&#39; mockSearch之后导入mock ,{{ 3}}指出: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. 导入所有依赖项,这将调用函数的模拟工厂以提供组件
  4. 将值分配给mockSearch
  5. 当第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);
    });
    

答案 1 :(得分:1)

就我而言,出现此错误是因为我无法正确实现模拟。

我的失败代码:

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

当它应该是箭头功能时...

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

答案 2 :(得分:0)

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

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

    });