【问题标题】:jest.fn() claims not to have been called, but hasjest.fn() 声称没有被调用,但有
【发布时间】:2019-07-20 07:34:39
【问题描述】:

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

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

【问题讨论】:

  • 你能详细说明一下吗? “它适用于我模拟的另一个函数”是什么意思?
  • 'someOtherAction' 也被 jest.fn() 模拟,并且被正确调用。 @brian-lives-outdoors 下面的答案似乎解释了真正的问题是什么。

标签: vue.js vuejs2 jestjs vuex


【解决方案1】:

这怎么可能?

expectselectElement 有机会运行之前运行并失败。


详情

消息队列

JavaScript 使用message queue。下一条开始之前的当前消息runs to completion

PromiseJobs 队列

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

异步/等待

asyncawait 只是 syntactic sugar over promises and generators。在 Promise 上调用 await 实质上是将函数的其余部分包装在回调中,以便在 Promise 解析时在 PromiseJobs 中安排。

会发生什么

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

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

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


解决方案

确保调用selectElementPromise 回调在运行expect 之前已经运行。

如果可能,最好返回Promise,以便测试可以直接await

如果这是不可能的,那么解决方法是在测试期间在已解决的Promise 上调用await,这基本上将其余测试排在 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
});

【讨论】:

  • 这比我希望的信息量大得多。谢谢!我最终删除了 async/await,因为这里似乎没有很好的理由。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-07
  • 2021-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多