Jest:Timer 和 Promise 效果不佳.(setTimeout 和异步函数) [英] Jest: Timer and Promise don't work well. (setTimeout and async function)

查看:23
本文介绍了Jest:Timer 和 Promise 效果不佳.(setTimeout 和异步函数)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

对此代码的任何想法

jest.useFakeTimers() 

it('simpleTimer', async () => {
  async function simpleTimer(callback) {
    await callback()    // LINE-A without await here, test works as expected.
    setTimeout(() => {
      simpleTimer(callback)
    }, 1000)
  }

  const callback = jest.fn()
  await simpleTimer(callback)
  jest.advanceTimersByTime(8000)
  expect(callback).toHaveBeenCalledTimes(9)
}

```

失败

Expected mock function to have been called nine times, but it was called two times.

但是,如果我从 LINE-A 中删除 await,则测试通过.

However, If I remove await from LINE-A, the test passes.

Promise 和 Timer 不能正常工作吗?

Does Promise and Timer not work well?

我认为开玩笑的原因可能是等待第二个承诺得到解决.

I think the reason maybe jest is waiting for second promise to resolve.

推荐答案

是的,您走在正确的轨道上.

Yes, you're on the right track.

await simpleTimer(callback) 将等待 simpleTimer() 返回的 Promise 解析,以便 callback() 第一次被调用并且 setTimeout() 也被调用.jest.useFakeTimers() setTimeout() 替换为一个模拟,因此模拟记录它是用 [ () =>{ simpleTimer(callback) }, 1000 ].

await simpleTimer(callback) will wait for the Promise returned by simpleTimer() to resolve so callback() gets called the first time and setTimeout() also gets called. jest.useFakeTimers() replaced setTimeout() with a mock so the mock records that it was called with [ () => { simpleTimer(callback) }, 1000 ].

jest.advanceTimersByTime(8000) 运行 () =>{ simpleTimer(callback) } (since 1000 < 8000) 调用 setTimer(callback) 第二次调用 callback() 并返回创建的 Promise通过 await.setTimeout()setTimer(callback) 已在PromiseJobs 队列 中排队,没有机会运行.

jest.advanceTimersByTime(8000) runs () => { simpleTimer(callback) } (since 1000 < 8000) which calls setTimer(callback) which calls callback() the second time and returns the Promise created by await. setTimeout() does not run a second time since the rest of setTimer(callback) is queued in the PromiseJobs queue and has not had a chance to run.

expect(callback).toHaveBeenCalledTimes(9) 未能报告 callback() 只调用了两次.

expect(callback).toHaveBeenCalledTimes(9) fails reporting that callback() was only called twice.

这是个好问题.它引起了人们对 JavaScript 的一些独特特征及其背后工作原理的关注.

This is a good question. It draws attention to some unique characteristics of JavaScript and how it works under the hood.

消息队列

JavaScript 使用消息队列.每条消息在运行完成之前运行时返回队列以检索下一条消息.setTimeout() 等函数 向队列.

JavaScript uses a message queue. Each message is run to completion before the runtime returns to the queue to retrieve the next message. Functions like setTimeout() add messages to the queue.

作业队列

ES6 引入了作业队列 和所需的作业队列之一是 PromiseJobs,它处理作为对 Promise 结算的响应的作业".此队列中的任何作业都在当前消息完成后和下一条消息开始之前运行.then() 将在 PromiseJobs 中的作业排队,当它被调用的 Promise 解决时.

ES6 introduces Job Queues and one of the required job queues is PromiseJobs which handles "Jobs that are responses to the settlement of a Promise". Any jobs in this queue run after the current message completes and before the next message begins. then() queues a job in PromiseJobs when the Promise it is called on resolves.

异步/等待

async/await 是只是承诺和生成器的语法糖.async 总是返回一个 Promise 并且 await 本质上将函数的其余部分包装在一个 then 回调中,该回调附加到它所给定的 Promise.

async / await is just syntactic sugar over promises and generators. async always returns a Promise and await essentially wraps the rest of the function in a then callback attached to the Promise it is given.

定时器模拟

Timer Mocks用 mock>setTimeout() 替换 setTimeout() 等函数当 jest.useFakeTimers() 被调用时.这些模拟记录了它们被调用的参数.然后当 AdvanceTimersByTime() 被称为循环运行,它同步调用本应在经过时间安排的任何回调,包括在运行回调时添加的任何回调.

Timer Mocks work by replacing functions like setTimeout() with mocks when jest.useFakeTimers() is called. These mocks record the arguments they were called with. Then when jest.advanceTimersByTime() is called a loop runs that synchronously calls any callbacks that would have been scheduled in the elapsed time, including any that get added while running the callbacks.

换句话说,setTimeout() 通常将消息排队,这些消息必须等到当前消息完成后才能运行.Timer Mocks 允许回调在当前消息中同步运行.

In other words, setTimeout() normally queues messages that must wait until the current message completes before they can run. Timer Mocks allow the callbacks to be run synchronously within the current message.

以下是演示上述信息的示例:

Here is an example that demonstrates the above information:

jest.useFakeTimers();

test('execution order', async () => {
  const order = [];
  order.push('1');
  setTimeout(() => { order.push('6'); }, 0);
  const promise = new Promise(resolve => {
    order.push('2');
    resolve();
  }).then(() => {
    order.push('4');
  });
  order.push('3');
  await promise;
  order.push('5');
  jest.advanceTimersByTime(0);
  expect(order).toEqual([ '1', '2', '3', '4', '5', '6' ]);
});

<小时>

如何让 Timer Mocks 和 Promises 发挥出色

Timer Mocks 将同步执行回调,但这些回调可能会导致作业在 PromiseJobs 中排队.

幸运的是,让 PromiseJobs 中的所有待处理作业在 async 测试中运行实际上很容易,您需要做的就是调用 await Promise.resolve().这实际上会将测试的其余部分排在 PromiseJobs 队列的末尾,并让队列中的所有内容先运行.

Fortunately it is actually quite easy to let all pending jobs in PromiseJobs run within an async test, all you need to do is call await Promise.resolve(). This will essentially queue the remainder of the test at the end of the PromiseJobs queue and let everything already in the queue run first.

考虑到这一点,这里是测试的工作版本:

With that in mind, here is a working version of the test:

jest.useFakeTimers() 

it('simpleTimer', async () => {
  async function simpleTimer(callback) {
    await callback();
    setTimeout(() => {
      simpleTimer(callback);
    }, 1000);
  }

  const callback = jest.fn();
  await simpleTimer(callback);
  for(let i = 0; i < 8; i++) {
    jest.advanceTimersByTime(1000);
    await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run
  }
  expect(callback).toHaveBeenCalledTimes(9);  // SUCCESS
});

这篇关于Jest:Timer 和 Promise 效果不佳.(setTimeout 和异步函数)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆