javascript - Hur man använder JEST för att testa tidsförloppet av rekursiva funktioner


Jag skriver ett test med JEST. Jag vet inte hur man testar löftesrekursion i JEST.

I detta test är retry-funktionen som utför rekursion målet för testet tills löftet är uppfyllt.

export function retry<T>(fn: () => Promise<T>, limit: number = 5, interval: number = 1000): Promise<T> {
  return new Promise((resolve, reject) => {
    fn()
      .then(resolve)
      .catch((error) => {
        setTimeout(() => {
          // Reject if the upper limit number of retries is exceeded
          if (limit === 1) {
            reject(error);

            return;
          }
          // Performs recursive processing of callbacks for which the upper limit number of retries has not been completed
          try {
            resolve(retry(fn, limit - 1, interval));
          } catch (err) {
            reject(err);
          }
        }, interval);
      });
  });
}

Utför följande test på ovanstående omsökningsfunktion.

  1. retry() löses vid tredje körningen. Första, andra och tredje gången kallas var 1000 sekund respektive.

Jag trodde att det skulle vara som följer när man skriver dessa i JEST.

jest.useFakeTimers();

describe('retry', () => {
  // Timer initialization for each test
  beforeEach(() => {
    jest.clearAllTimers();
  });
  // Initialize timer after all tests
  afterEach(() => {
    jest.clearAllTimers();
  });

  test('resolve on the third call', async () => {
    const fn = jest
      .fn()
      .mockRejectedValueOnce(new Error('Async error'))
      .mockRejectedValueOnce(new Error('Async error'))
      .mockResolvedValueOnce('resolve');

    // Test not to be called
    expect(fn).not.toBeCalled();
    // Mock function call firs execution
    await retry(fn);
    // Advance Timer for 1000 ms and execute for the second time
    jest.advanceTimersByTime(1000);
    expect(fn).toHaveBeenCalledTimes(2);
    // Advance Timer for 1000 ms and execute for the third time
    jest.advanceTimersByTime(1000);
    expect(fn).toHaveBeenCalledTimes(3);

    await expect(fn).resolves.toBe('resolve');
  });

});

Som ett resultat misslyckades det på följande fel.

● retry › resolve on the third call
Timeout - Async callback was not invoked within the 30000ms timeout specified by jest.setTimeout.Error: 

    > 16 |   test('resolve on the third call', async () => {
         |   ^
      17 |     jest.useFakeTimers();
      18 |     const fn = jest
      19 |       .fn()

Jag tror att det kommer att vara hanterbart i JEST:s miljö när det gäller detta fel. Men i grunden vet jag inte hur man testar asinchron rekursiv bearbetning av promise i JEST.

Svar #1

Din funktion är så svår att testa med timer.

När du anropar await retry(fn); betyder det att du kommer vänta tills retry returnerar ett värde, men setTimeout har blockerats tills du anropar jest.advanceTimersByTime(1000); => detta är huvudorsaken, eftersom jest.advanceTimersByTime(1000); aldrig har anropats.

Du kan se mitt exempel, det fungerar bra med jests falska timers.

  test("timing", async () => {
    async function simpleTimer(callback) {
      await callback();
      setTimeout(() => {
        simpleTimer(callback);
      }, 1000);
    }

    const callback = jest.fn();
    await simpleTimer(callback); // it does not block any things
    for (let i = 0; i < 8; i++) {
      jest.advanceTimersByTime(1000); // then, this line will be execute
      await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run
    }
    expect(callback).toHaveBeenCalledTimes(9);  // SUCCESS
  });

Jag tycker att du kan hoppa över testtimer-detaljerna, testa bara din logik: fn har kallats 3 gånger, och slutligen returnerar den "resolve"

test("resolve on the third call", async () => {
    const fn = jest
      .fn()
      .mockRejectedValueOnce(new Error("Async error"))
      .mockRejectedValueOnce(new Error("Async error"))
      .mockResolvedValueOnce("resolve");

    // expect.assertions(3);

    // Test not to be called
    expect(fn).not.toBeCalled();
    // Mock function call firs execution

    const result = await retry(fn);

    expect(result).toEqual("resolve");

    expect(fn).toHaveBeenCalledTimes(3);
  });

Obs: ta bort alla dina falska timers - jest.useFakeTimers

Svar #2

Dokumentationen för Jest antyder att testning av löften är ganska enkelt (https://jestjs.io/docs/en/asynchronous)

De ger detta exempel (anta att fetchData returnerar ett löfte precis som din retry-funktion)

test('the data is peanut butter', () => {
  return fetchData().then(data => {
    expect(data).toBe('peanut butter');
  });
});

Relaterade artiklar