Javascript 不能用玩笑模拟模块,并测试函数调用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39633919/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Cannot mock a module with jest, and test function calls
提问by ghusse
I create a project using create-app-component, which configures a new app with build scripts (babel, webpack, jest).
我使用create-app-component创建了一个项目,它使用构建脚本(babel、webpack、jest)配置一个新的应用程序。
I wrote a React component that I'm trying to test. The component is requiring another javascript file, exposing a function.
我写了一个我正在尝试测试的 React 组件。该组件需要另一个 javascript 文件,公开一个函数。
My search.js file
我的 search.js 文件
export {
search,
}
function search(){
// does things
return Promise.resolve('foo')
}
My react component:
我的反应组件:
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}
/>
}
}
In my unit tests, I want to check that the method search
was called with the correct arguments.
在我的单元测试中,我想检查是否search
使用正确的参数调用了该方法。
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);
})
})
});
I already had a hard time to figure out how to make jest.mock
work, because it requires variables to be prefixed by mock
.
我已经很难弄清楚如何jest.mock
工作,因为它要求变量以mock
.
But if I want to test arguments to the method search
, I need to make the mocked function available in my tests.
但是如果我想测试方法的参数search
,我需要在我的测试中提供模拟函数。
If I transform the mocking part, to use a variable:
如果我转换模拟部分,使用一个变量:
const mockSearch = jest.fn(() => mockPromise)
jest.mock('./search.js', () => {
return { search: mockSearch};
});
I get this error:
我收到此错误:
TypeError: (0 , _search.search) is not a function
TypeError: (0 , _search.search) 不是函数
Whatever I try to have access to the jest.fn
and test the arguments, I cannot make it work.
无论我尝试访问jest.fn
和测试参数,我都无法使其工作。
What am I doing wrong?
我究竟做错了什么?
回答by Tomty
The problem
问题
The reason you're getting that error has to do with how various operations are hoisted.
您收到该错误的原因与各种操作的提升方式有关。
Even though in your original code you only import SearchContainer
afterassigning a value to mockSearch
and calling jest's mock
, the specspoint out that: Before instantiating a module, all of the modules it requested must be available
.
即使在您的原始代码中,您只是SearchContainer
在为mockSearch
jest赋值并调用 jest之后才导入mock
,但规范指出:Before instantiating a module, all of the modules it requested must be available
.
Therefore, at the time SearchContainer
is imported, and in turn imports search
, your mockSearch
variable is still undefined.
因此,在SearchContainer
导入时,然后导入search
,您的mockSearch
变量仍然未定义。
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 higherthan the imports, so that mocking will work.
人们可能会觉得这很奇怪,因为它似乎也暗示search.js
尚未被嘲笑,因此嘲笑根本不起作用。幸运的是,(babel-)jest 确保将调用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:
尽管如此,mockSearch
由模拟函数引用的 的赋值不会随着mock
调用而提升。因此,相关操作的顺序将类似于:
- Set a mock factory for
./search.js
- Import all dependencies, which will call the mock factory for a function to give the component
- Assign a value to
mockSearch
- 设置一个模拟工厂
./search.js
- 导入所有依赖项,这将调用模拟工厂的函数来提供组件
- 赋值给
mockSearch
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.
当第 2 步发生时,search
传递给组件的函数将是未定义的,第 3 步的赋值来不及改变它。
Solution
解决方案
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.
如果您将模拟函数创建为mock
调用的一部分(这样它也会被提升),那么它在由组件模块导入时将具有有效值,如您早期的示例所示。
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';
With:
和:
const { search } = require.requireMock('./search.js');
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
).
这不应该产生任何功能上的差异,但可能会使您正在做的事情更加明确(并且应该可以帮助任何使用类型检查系统(例如 Flow)的人,因此它不会认为您正在尝试调用模拟函数在原来的search
)。
Additional note
附加说明
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:
如果您需要模拟的是模块本身的默认导出,则所有这些都是绝对必要的。否则(正如@publicJorn 指出的那样),您可以简单地在测试中重新分配特定的相关成员,如下所示:
import * as search from './search.js';
beforeEach(() => {
search.search = jest.fn(() => mockPromise);
});
回答by Nabil Freeman
In my case, I got this error because I failed to implement the mock correctly.
就我而言,我收到此错误是因为我未能正确实现模拟。
My failing code:
我的失败代码:
jest.mock('react-native-some-module', mockedModule);
jest.mock('react-native-some-module', mockedModule);
When it should have been an arrow function...
当它应该是一个箭头函数时......
jest.mock('react-native-some-module', () => mockedModule);
jest.mock('react-native-some-module', () => mockedModule);
回答by swandog
When mocking an api call with a response remember to async() => your test and await the wrapper update. My page did the typical componentDidMount => make API call => positive response set some state...however the state in the wrapper did not get updated...async and await fix that...this example is for brevity...
当使用响应模拟 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);
});