不能用玩笑模拟模块,也不能测试函数调用
Cannot mock a module with jest, and test function calls
我使用 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) is not a function
无论我尝试访问 jest.fn
并测试参数,我都无法使其工作。
我做错了什么?
问题
您收到该错误的原因与提升各种操作的方式有关。
即使在您的原始代码中您仅在 为 mockSearch
赋值并调用 jest 的 mock
之后导入 SearchContainer
,specs指出:Before instantiating a module, all of the modules it requested must be available
.
因此,在导入 SearchContainer
并依次导入 search
时,您的 mockSearch
变量仍未定义。
有人可能会觉得这很奇怪,因为它似乎还暗示 search.js
尚未被模拟,因此模拟根本不起作用。幸运的是,(babel-)jest 确保提升对 mock
和类似函数的调用,甚至比导入 更高 ,这样模拟就可以工作了。
然而,由模拟函数引用的 mockSearch
的赋值不会随 mock
调用一起提升。因此,相关操作的顺序将类似于:
- 为
./search.js
设置模拟工厂
- 导入所有依赖项,这将调用模拟工厂为函数提供组件
- 给
mockSearch
赋值
当第 2 步发生时,传递给组件的 search
函数将是未定义的,第 3 步的赋值来不及改变它。
解决方案
如果您创建模拟函数作为 mock
调用的一部分(这样它也会被提升),当它被组件模块导入时,它将具有一个有效值,因为您的早期示例显示。
正如您所指出的,当您想让模拟函数在您的测试中可用时,问题就开始了。对此有一个明显的解决方案:单独导入您已经模拟的模块。
既然你现在知道 jest mocking 实际上发生在导入之前,一个简单的方法是:
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);
});
当用响应模拟 api 调用时,请记住 async() => 您的测试并等待包装器更新。我的页面做了典型的 componentDidMount => make API call => positive response set some state...但是包装器中的状态没有得到更新...async 和 await 修复了...这个例子是简洁...
...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);
});
就我而言,我收到此错误是因为我未能正确实施模拟。
我的失败代码:
jest.mock('react-native-some-module', mockedModule);
当它应该是箭头函数时...
jest.mock('react-native-some-module', () => mockedModule);
我使用 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) is not a function
无论我尝试访问 jest.fn
并测试参数,我都无法使其工作。
我做错了什么?
问题
您收到该错误的原因与提升各种操作的方式有关。
即使在您的原始代码中您仅在 为 mockSearch
赋值并调用 jest 的 mock
之后导入 SearchContainer
,specs指出:Before instantiating a module, all of the modules it requested must be available
.
因此,在导入 SearchContainer
并依次导入 search
时,您的 mockSearch
变量仍未定义。
有人可能会觉得这很奇怪,因为它似乎还暗示 search.js
尚未被模拟,因此模拟根本不起作用。幸运的是,(babel-)jest 确保提升对 mock
和类似函数的调用,甚至比导入 更高 ,这样模拟就可以工作了。
然而,由模拟函数引用的 mockSearch
的赋值不会随 mock
调用一起提升。因此,相关操作的顺序将类似于:
- 为
./search.js
设置模拟工厂
- 导入所有依赖项,这将调用模拟工厂为函数提供组件
- 给
mockSearch
赋值
当第 2 步发生时,传递给组件的 search
函数将是未定义的,第 3 步的赋值来不及改变它。
解决方案
如果您创建模拟函数作为 mock
调用的一部分(这样它也会被提升),当它被组件模块导入时,它将具有一个有效值,因为您的早期示例显示。
正如您所指出的,当您想让模拟函数在您的测试中可用时,问题就开始了。对此有一个明显的解决方案:单独导入您已经模拟的模块。
既然你现在知道 jest mocking 实际上发生在导入之前,一个简单的方法是:
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);
});
当用响应模拟 api 调用时,请记住 async() => 您的测试并等待包装器更新。我的页面做了典型的 componentDidMount => make API call => positive response set some state...但是包装器中的状态没有得到更新...async 和 await 修复了...这个例子是简洁...
...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);
});
就我而言,我收到此错误是因为我未能正确实施模拟。
我的失败代码:
jest.mock('react-native-some-module', mockedModule);
当它应该是箭头函数时...
jest.mock('react-native-some-module', () => mockedModule);