tim-kos / node-retry

Abstraction for exponential and custom retry strategies for failed operations.
Other
1.22k stars 80 forks source link

node-retry retry.operation is being called only once with jest.fn promise reject mocks #79

Closed mario128mex closed 3 years ago

mario128mex commented 3 years ago

Hi I have the following code

retryer.js

const retry = require('retry');

const retryer = async (call, options) =>
  new Promise((resolve, reject) => {
    const { retries: maxRetries } = options
    const operation = retry.operation(options)

    operation.attempt(async currentAttempt => {
      try {
        const response = await call()
        resolve(response)
      } catch (err) {
        if (currentAttempt === maxRetries) {
          console.error('max amount of calls reached, aborting')
          return reject(err)
        }

        console.error('failed call number: ', currentAttempt);
        operation.retry(err)
      }
    })
  })

module.exports = retryer;

then I run this code which send a 404 on purpose main.js

const axios = require('axios');
const retryer = require('./retryer')

const url = 'https://pokeapi.co/api/v2'

const makeCall = (resource) => {
  return axios({
    method: 'get',
    url: url + resource
  })
}

const main = async () => {
  const retryerOptions = {
    retries: 5,
    minTimeout: 1 * 1000,
    maxTimeout: 5 * 1000
  }

  console.log(`making call to ${url} ...`);

  const response = await retryer(() => makeCall('/pokemons'), retryerOptions)

  console.log('my data', response.data);
  console.log('finishing..');
}

main()

the retryer behaves as expected Screen Shot 2021-06-29 at 12 04 29 p m

but in my tests when I pass a mocked function that always returns a rejected promise, the retryer only do one call and then exits, why is this? retryer.test.js

const retry = require('../retryer.js')

const APICallRetryerOptions = {
  retries: 5,
  minTimeout: 1000,
  maxTimeout: 5000
}

describe('node-retry test issue', () => {
  it('should throw and error when the max amount of attempts is reached', async () => {
    const mockAxiosError = {
      isAxiosError: true,
      config: {
        url: 'bad-site.com',
        method: 'get'
      },
      response: {
        status: 500,
        statusText: 'some weird error',
        data: {
          Error: 'Mock error!'
        }
      }
    }

    const mockApiCall = jest
      .fn()
      .mockRejectedValueOnce(mockAxiosError)
      .mockRejectedValueOnce(mockAxiosError)
      .mockRejectedValueOnce(mockAxiosError)
      .mockRejectedValueOnce(mockAxiosError)
      .mockRejectedValueOnce(mockAxiosError)

    retry(() => mockApiCall(), APICallRetryerOptions)

    expect(mockApiCall).toHaveBeenCalledTimes(5)
  })
})

the above code, as I said before, calls mockAPICall once and then exits, I've tried to debug the test to see why that happens but I could figure it out, also I've done some research but I couldn't find a similar issue image

I also tried to change mockAPICall declaration to const mockApiCall = jest.fn(() => Promise.reject(mockAxiosError)) but still getting just one call

can you point out what I'm doing wrong? Thanks!

mario128mex commented 3 years ago

I just found out the issue, I needed to await the retry call on my test here

describe('node-retry test issue', () => {
  it('should throw and error when the max amount of attempts is reached', async () => {
    const mockAxiosError = {
      isAxiosError: true,
      config: {
        url: 'bad-site.com',
        method: 'get'
      },
      response: {
        status: 500,
        statusText: 'some weird error',
        data: {
          Error: 'Mock error!'
        }
      }
    }

    const mockApiCall = jest
      .fn()
      .mockRejectedValueOnce(mockAxiosError)
      .mockRejectedValueOnce(mockAxiosError)
      .mockRejectedValueOnce(mockAxiosError)
      .mockRejectedValueOnce(mockAxiosError)
      .mockRejectedValueOnce(mockAxiosError)

    try {
      await retry(() => mockApiCall(), APICallRetryerOptions)
    } catch (err) {
      expect(err.isAxiosError).toBeTruthy()
      expect(err.response.data.Error).toEqual('Mock error!')
    }

    expect(mockApiCall).toHaveBeenCalledTimes(5)
  })
})