Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write unit test for exponential backoff retry method implemented in javascript with jest

Trying to test an exponential backoff method which retries API request via fetch 5 times, will have the following delays: [1 ms, 10 ms, 100 ms, 1 s, 10 s], I am not able to successfully test it.

Methods

export const delay = retryCount => new Promise(resolve => setTimeout(resolve, 10 ** retryCount));

/**
 * Fetching with delay when api call fails,
 * first 5 retries will have the following delays: [1 ms, 10 ms, 100 ms, 1 s, 10 s]
 */
export const fetchRetry = async (options, retryCount = 0, lastError = null) => {
  if (retryCount > 5) throw new Error(lastError);
  try {
    return await fetch(options);
  } catch (error) {
    await delay(retryCount);
    return fetchRetry(options, retryCount + 1, error);
  }
};

Test

import fetchMock from 'jest-fetch-mock';

import { delay, fetchRetry } from './retry';

// This can be set up globally if needed
fetchMock.enableMocks();

beforeEach(() => {
  fetch.resetMocks();
});

describe('fetchWithExponentialBackoffRetry', () => {
  it('fetch is called once when response is 200', done => {
    fetch.mockResponseOnce(
      JSON.stringify({
        success: true,
        message: 'OK',
        code: 200,
        data: 'c86e795f-fe70-49be-a8fc-6876135ab109',
      }),
    );

    setTimeout(function() {
      fetchRetry({
        inventory_type_id: 2,
        advertiser_id: 2315,
        file: null,
      });
      expect(fetch).toHaveBeenCalledTimes(1);
      done();
    }, 0);
  });

  it('fetch is called 5 times when response is returns failure', done => {
    fetch.mockReject(() => Promise.reject(new Error('Rejected')));

    setTimeout(function() {
      fetchRetry({
        inventory_type_id: 2,
        advertiser_id: 2315,
        file: null,
      });
      expect(fetch).toHaveBeenCalledTimes(5);
      done();
    }, 100000);
  });
});

I am getting the following error

console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29 Error: Error: connect ECONNREFUSED 127.0.0.1:8

I think it has to do we delay method I have to incorporate setTimeout somehow in my tests, now sure how to mock it here. I would appreciate the help.

like image 963
RecipeCreator Avatar asked Dec 01 '25 03:12

RecipeCreator


1 Answers

You're testing the outcome of an async function, so you need to make your tests async too - you're not doing so - i.e. you're not awaiting fetchRetry and are therefore just calling done() synchronously.

I think the error is being caused by the use of setTimeout here. This looks like a race condition bug so hard to be sure without debugging, but from reading the code it looks like the issue is that you're mocking fetch with jest-fetch-mock, but since your test code runs synchronously and you have...

beforeEach(() => {
  fetch.resetMocks();
});

...it is probably unsetting the fetch mock before the call in the test that runs first is made and so it's actually calling your API - hence the error.

Making the tests async is fairly simple - the docs are here - and using async/await it's even cleaner since you don't actually need to use done - the test is just done when the promise resolves (or rejects).

Basically your test code will be mostly the same, except you'll be awaiting your calls to fetchRetry, like so:

it('fetch is called once when response is 200', async () => {
  fetch.mockResponseOnce(...)
     
  await fetchRetry({ ... }) 
  expect(fetch).toHaveBeenCalledTimes(1);
});

it('fetch is called 5 times when response is returns failure', async () => {
  fetch.mockReject(...);

  try {
    await fetchRetry({ ... });
  } catch (err) {
    // eventual error expected as response failure is mocked
    expect(fetch).toHaveBeenCalledTimes(5);  
  }  
});
like image 165
davnicwil Avatar answered Dec 02 '25 18:12

davnicwil