我正在测试一个Vue组件,当路由中存在某个参数时,该组件会在我的Vuex存储中调用某个操作。我正在用jest.fn()
嘲笑这个动作。
这是组件的相关代码:
await this.$store.dispatch('someOtherAction');
if (this.$route.params && this.$route.params.id) {
this.$store.dispatch('selection/selectElement', parseInt(this.$route.params.id, 10));
}
这是模拟功能:
someOtherAction = jest.fn();
selectElement = jest.fn(() => console.log("selectElement has been called"));
我的测试:
it('selects element if passed in route', async () => {
const $route = {params: {id: '256'}};
const wrapper = shallowMount(AbcModel, {
mocks: {$route},
store, localVue
});
expect(someOtherAction).toHaveBeenCalled();
expect(selectElement).toHaveBeenCalled();
});
在输出中,我可以看到'selectElement已被调用'。显然它被称为。然而,expect(selectElement).toHaveBeenCalled()
失败了。
这怎么可能?它与我嘲笑的另一个函数一起工作正常。替换我模拟函数的顺序无关紧要。删除其他函数被调用的期望也无关紧要,因此它看起来不像是碰撞。
这怎么可能?
在expect
有机会跑之前,selectElement
跑了并且失败了。
消息队列
JavaScript使用message queue。当前消息runs to completion在下一个消息开始之前。
PromiseJobs队列
ES6引入了PromiseJobs queue,它处理的工作“是对Promise的解决”。 PromiseJobs队列中的所有作业在当前消息完成之后和下一条消息开始之前运行。
异步/等待
async
和await
只是syntactic sugar over promises and generators。在await
解析时,在Promise
上调用Promise
实际上将函数的其余部分包装在PromiseJobs中的调用中。
怎么了
您的测试开始作为当前运行的消息运行。调用shallowMount
加载你的组件,该组件一直运行到调用await this.$store.dispatch('someOtherAction');
的someOtherFunction
,然后基本上将函数的其余部分排队为Promise
回调,以便在Promise
结算时在PromiseJobs队列中进行调度。
执行然后返回到运行两个expect
语句的测试。自someOtherFunction
被召唤以来,第一个过去了,但是第二个失败,因为selectElement
尚未运行。
然后,当前运行的消息完成,然后运行PromiseJobs队列中的挂起作业。调用selectElement
的回调在队列中,因此它运行并调用selectElement
,它会记录到控制台。
解
确保在运行Promise
之前调用selectElement
的expect
回调已经运行。
只要有可能,它是理想的返回Promise
所以测试可以直接await
它。
如果这是不可能的话,那么解决方法是在测试期间在已解析的await
上调用Promise
,它基本上将其余的测试排在PromiseJobs队列的后面,并允许任何挂起的Promise
回调首先运行:
it('selects element if passed in route', async () => {
const $route = {params: {id: '256'}};
const wrapper = shallowMount(AbcModel, {
mocks: {$route},
store, localVue
});
expect(someOtherFunction).toHaveBeenCalled();
// Ideally await the Promise directly...
// but if that isn't possible then calling await Promise.resolve()
// queues the rest of the test at the back of PromiseJobs
// allowing any pending callbacks to run first
await Promise.resolve();
expect(selectElement).toHaveBeenCalled(); // SUCCESS
});