javascript - Jest unit test - How do I call through async function in a setTimeout repeating function - Stack Overflow

UPDATE: Safe to say this is basically a duplicate of Jest: Timer and Promise dont work well. (setTimeou

UPDATE: Safe to say this is basically a duplicate of Jest: Timer and Promise don't work well. (setTimeout and async function)


I have a function that repeats itself after performing an asynchronous function. I want to verify that jest.advanceTimersOnTime(5000) continues to yield expect(doAsyncStuff).toHaveBeenCalledTimes(X);.

I observe that we can reach the call to repeat the function but it does not "go through" when asking jest to advance timers. It does work when you remove the async function preceding it. So it sounds like I have to get doAsyncStuff to "call through" or resolve some pending promise here?

Function

function repeatMe() {
    setTimeout(() => {
        doAsyncStuff.then((response) => {
            if (response) {
                console.log("I get here!");
                repeatMe();
            }
        })
    }, 5000);
}

Test

jest.useFakeTimers();

let doAsyncStuff = jest.spyOn(updater, 'doAsyncStuff');
doAsyncStuff.mockResolvedValue(true);

repeatMe();

jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(1);

jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(2); // Failed?

UPDATE: Safe to say this is basically a duplicate of Jest: Timer and Promise don't work well. (setTimeout and async function)


I have a function that repeats itself after performing an asynchronous function. I want to verify that jest.advanceTimersOnTime(5000) continues to yield expect(doAsyncStuff).toHaveBeenCalledTimes(X);.

I observe that we can reach the call to repeat the function but it does not "go through" when asking jest to advance timers. It does work when you remove the async function preceding it. So it sounds like I have to get doAsyncStuff to "call through" or resolve some pending promise here?

Function

function repeatMe() {
    setTimeout(() => {
        doAsyncStuff.then((response) => {
            if (response) {
                console.log("I get here!");
                repeatMe();
            }
        })
    }, 5000);
}

Test

jest.useFakeTimers();

let doAsyncStuff = jest.spyOn(updater, 'doAsyncStuff');
doAsyncStuff.mockResolvedValue(true);

repeatMe();

jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(1);

jest.advanceTimersByTime(5000);
expect(doAsyncStuff).toHaveBeenCalledTimes(2); // Failed?
Share Improve this question edited Mar 22, 2019 at 17:22 ericjam asked Mar 22, 2019 at 14:40 ericjamericjam 1,5293 gold badges20 silver badges32 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4

So it sounds like I have to...resolve some pending promise here?

Yes, exactly.


The short answer is that a Promise callback gets queued in PromiseJobs by the then chained to the Promise returned by doAsyncStuff, and the way the test is written, that callback never has a chance to run until the test is already over.

To fix it, give the Promise callbacks a chance to run during your test:

updater.js

export const doAsyncStuff = async () => { };

code.js

import { doAsyncStuff } from './updater';

export function repeatMe() {
  setTimeout(() => {
    doAsyncStuff().then((response) => {
      if (response) {
        console.log("I get here!");
        repeatMe();
      }
    })
  }, 5000);
}

code.test.js

import * as updater from './updater';
import { repeatMe } from './code';

test('repeatMe', async () => {
  jest.useFakeTimers();

  let doAsyncStuff = jest.spyOn(updater, 'doAsyncStuff');
  doAsyncStuff.mockResolvedValue(true);

  repeatMe();

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(1);  // Success!

  await Promise.resolve();  // let callbacks in PromiseJobs run

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(2);  // Success!

  await Promise.resolve();  // let callbacks in PromiseJobs run

  jest.advanceTimersByTime(5000);
  expect(doAsyncStuff).toHaveBeenCalledTimes(3);  // Success!

  // ... and so on ...
});

The plete details of exactly what happens and why can be found in my answer here

Apparently Jest troubleshooting references this issue: we have set the definition to happen asynchronously on the next tick of the event loop. I guess this applies to events within the live code too, not just the written tests.

Doing jest.runAllTimers() will set things into an endless loop.

Adding jest.runAllTicks() will advance the tick so the above tests now work.

I'm still confused but I guess this is the answer.

jest.advanceTimersByTime(5000);
jest.runAllTicks();
expect(doAsyncStuff).toHaveBeenCalledTimes(1);

jest.advanceTimersByTime(5000);
jest.runAllTicks();
expect(doAsyncStuff).toHaveBeenCalledTimes(2); 

https://jestjs.io/docs/en/troubleshooting#defining-tests


Also required for this to work is to mock the implementation of doAsyncStuff because whatever async stuff inside doAsyncStuff also I think gets queued into the tick events.

            doAsyncStuff.mockImplementation(() => {
                return new Promise.resolve();
            });

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744926379a4601461.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信