0

Here's my function, its basically calling an API in a loop with a wait step so I don't hit the rate limit.

    import { doAsyncThing } from '../../../somewhere';

    export const myAsyncFunc = async () => {
      // An array of data
      const arrayData = await ['123', '456', '789'];
    
      for (const item of arrayData) {
        // Call an API
        await doAsyncThing(item);
        // Wait for 1 second
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
    };

Here's my test code

jest.mock('../../../somewhere');
jest.useFakeTimers();

test('a test', async () => {
  const funcPromise = myAsyncFunc();
  jest.runAllTimers();
  expect(myAsyncFunc).resolves.toBeUndefined();

  expect(doAsyncThing).toBeCalledTimes(2);
  expect(setTimeout).toBeCalledTimes(2);
});

However this doesn't work and produces this result

    expect(jest.fn()).toBeCalledTimes(expected)

    Expected number of calls: 2
    Received number of calls: 0

      25 |   expect(myAsyncFunc).resolves.toBeUndefined();
      26 |
    > 27 |   expect(doAsyncThing).toBeCalledTimes(2);

I'm pretty sure I can't await for myAsyncFunc to finish as the jest.useFakeTimers causes me to manually advance timers. However how can I advance timers if myAsyncFunc is not yet finished?

I'm a bit confused on how to test this function

Colin Cheung
  • 148
  • 2
  • 13
  • You forgot to await – Aluan Haddad May 22 '23 at 10:53
  • I originally used await but that causes a timeout, I suspect this is because I'm awaiting the loop to finish but I never get to advance the timers – Colin Cheung May 22 '23 at 11:55
  • `async` without `await` is highly problematic in any case, but in a situation like this, where your test call back is `async` and doesn't return a value, it's simply a bug. – Aluan Haddad May 22 '23 at 13:45
  • More broadly, not awaiting explains the behavior that you're observing. The test is considered over before the loop even executes. I don't know why that would cause a timeout, I usually test asynchronous methods using `npm:blue-tape` which doesn't have that problem but I'm sure you can configure the timeout or something – Aluan Haddad May 22 '23 at 13:46
  • 1
    I guess the answer you're looking for is similar to the ones found in [this question](https://stackoverflow.com/questions/52177631/jest-timer-and-promise-dont-work-well-settimeout-and-async-function/52196951#52196951). I'll try to adapt it to your specific use case later today, as I'm currently typing from my phone. – Moa Jun 14 '23 at 10:04
  • @Moa Oh this is great, it's the closest I've seen this work at least. I've hardcoded the arrayData to be of length 3 but my current problem is now somehow the for loop has to be i<5 for the test to pass. However at the very least this is working ```js const funcPromise = myAsyncFunc(); for (let i = 0; i < 5; i++) { jest.runAllTimers(); await Promise.resolve(); } expect(funcPromise).resolves.toBeUndefined(); expect(doAsyncThing).toBeCalledTimes(3); ``` – Colin Cheung Jun 15 '23 at 16:03

2 Answers2

1

Thanks to Moa for providing a similar issue

I've managed to fix the test so that the promises are awaited but for some very strange reason the loop has to run 5 times even though there are only 3 elements in the arrayData

Here's the updated test:

import { myAsyncFunc } from '.';
import { doAsyncThing } from '../../../somewhere';

jest.mock('../../../somewhere');
jest.useFakeTimers();

test('a test', async () => {
  const funcPromise = myAsyncFunc();

  for (let i = 0; i < 5; i++) {
    jest.runAllTimers();
    await Promise.resolve();
  }

  expect(doAsyncThing).resolves.toBeUndefined();
  expect(myAsyncFunc).toBeCalledTimes(3);
});

Colin Cheung
  • 148
  • 2
  • 13
  • 1
    Well done! I've ended up not writing an answer at all. Glad you managed to sort it out. Happy coding! – Moa Jun 21 '23 at 13:10
0

Wouldn't awaiting the result work, after firing the timers?

jest.useFakeTimers();
test('a test', async () => {
  const funcPromise = myAsyncFunc();
  jest.runAllTimers();
  await funcPromise;
  expect(myAsyncFunc).resolves.toBeUndefined();

  expect(doAsyncThing).toBeCalledTimes(2);
  expect(setTimeout).toBeCalledTimes(2);
});
Salketer
  • 14,263
  • 2
  • 30
  • 58
  • I've tried this before but it just times out, extending the timeout doesn't solve this issue either ``` thrown: "Exceeded timeout of 5000 ms for a test. Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout." 4 | jest.mock('../../../pathToMyAsyncFunc'); 5 | jest.useFakeTimers(); > 6 | test('a test', async () => { | ^ 7 | const funcPromise = myAsyncFunc(); 8 | jest.runAllTimers(); 9 | await funcPromise; ``` – Colin Cheung Jun 15 '23 at 15:51
  • Your function is calling getArrayData(), would it be possible that this function takes too long? – Salketer Jun 16 '23 at 09:11
  • I've updated the getArrayData call to be a hardcoded array of 3 elements, that wasn't the issue, it seems to be what @Moa suggested in this comment https://stackoverflow.com/questions/76282237/how-to-test-settimeout-and-async-functions-in-a-loop-jest#comment134839170_76282237 – Colin Cheung Jun 16 '23 at 10:35