首页 > 解决方案 > jest.fn() 声称没有被调用,但有

问题描述

我正在测试一个 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()失败了。

这怎么可能?它适用于我模拟的另一个功能。替换我模拟函数的顺序并不重要。消除调用另一个函数的期望也没关系,所以它看起来不像是冲突。

标签: vue.jsvuejs2jestjsvuex

解决方案


这怎么可能?

之前的expect运行和失败selectElement都有机会运行。


细节

消息队列

JavaScript 使用消息队列。当前消息在下一条消息开始之前运行完成。

PromiseJobs 队列

ES6 引入了PromiseJobs 队列,它处理“对 Promise 的解决作出响应”的作业。PromiseJobs 队列中的任何作业都在当前消息完成之后和下一条消息开始之前运行。

异步/等待

async并且await只是promises 和 generators 的语法糖。调用awaitaPromise基本上将函数的其余部分包装在回调中,以便在解析时在 PromiseJobs 中安排Promise

怎么了

您的测试作为当前正在运行的消息开始运行。调用shallowMount会加载您的组件,该组件会运行直到await this.$store.dispatch('someOtherAction');调用someOtherFunction,然后实质上将函数的其余部分作为Promise回调排队,以便在解决时安排在 PromiseJobs 队列中Promise

然后执行返回到运行这两个expect语句的测试。第一个通过,因为someOtherFunction已被调用,但第二个失败,因为selectElement尚未运行。

当前正在运行的消息然后完成,然后运行 ​​PromiseJobs 队列中的待处理作业。调用的回调selectElement在队列中,因此它运行并调用selectElement记录到控制台的日志。


解决方案

确保Promise调用的回调selectElement在运行expect.

只要有可能,最好将其返回,Promise以便测试可以await直接使用它。

如果这是不可能的,那么一种解决方法是在测试期间调用awaitresolve 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
});

推荐阅读