nock does not intercept follow-up calls when using axios-retry

558 views Asked by At

In short:

  • When doing normal calls, nock works perfectly
  • But when testing a request that triggers axios-retry, nock fails when the first retry attempt happens (based on logging)

Does nock support retries? If so, how should the test be setup?

Client:


const getClient = (authorizationToken: string | undefined, target: ApiParams) =>
  axios.create({
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      ...(!!authorizationToken && { Authorization: `Bearer ${authorizationToken}` }),
      'x-api-key': target.apiKey || 'missing',
    },
    timeout: API_TIMEOUT_MILLISECONDS,
  });

export const sendRequest = ({ someParams }) => {
  const client = getClient(authorizationToken, target);

  axiosRetry(client, {
    retries: API_MAX_RETRIES,
    retryDelay: (retryCount) => {
      logger.info('retrying request', retryCount);
      return retryCount * API_RETRY_INTERVAL_MS;
    },
    retryCondition: (error) => {
      // There's more in this, but it's not relevant for the question
      return Number(error.response.status) >= 500;
    },
  });
}

Simplified implementation that I test:

export const upsertExclusionList = async () => {
  const response: ApiClientResult = sendRequest({ someParams });
  if (response.data) {
    return response.data;
  } else {
    throw Error('No data');
  }
}

Test that works:

  it('processes valid request', async () => {
    nock(serverBaseUrl).put('/exclusion-lists', { some: 'data' }).query({ callerId: 'Tester', role: 'Tester' }).reply(200, { success: true });
    const response: ExclusionList = await upsertExclusionList({ someParams });
    expect(response).toEqual({ some: 'thing' });
  });

Test that fails (this test triggers retries):

  it('handles a 500 response from the API', async () => {
    nock(serverBaseUrl).put('/exclusion-lists', { some: 'data' }).query({ callerId: 'Tester', role: 'Tester' }).reply(500, { error: 'Some error' });
    await expect(upsertExclusionList({ someParams }).toBeCalledTimes(4);
    // Also did this:
    // await expect(upsertExclusionList({ someParams }).rejects.toEqual('No data');
  });

But what happens is I get this:

    Attempted to log "Call to put exclusion list API (https://api.example.com/exclusion-lists?callerId=Tester&role=Tester) failed: Nock: No match for request {
      "method": "PUT",
      "url": "https://api.example.com/exclusion-lists?callerId=Tester&role=Tester",
      "headers": {
        "accept": "application/json",
        "content-type": "application/json",
        "authorization": "Bearer unit-test",
        "x-api-key": "xxx",
        "user-agent": "axios/1.2.0-alpha.1",
        "content-length": "93",
        "accept-encoding": "gzip, deflate, br"
      },
      "body": "{\"some\":\"data\"}"
    } (2080 ms)".

Versions:

node 18.13.0

"axios-retry": "3.3.1",
"nock": "13.3.0",
"jest": "28.1.3",
"ts-jest": "28.0.8",
"typescript": "4.3.5"

So, how can I get nock to work with axios-retry?

1

There are 1 answers

0
Matt R. Wilson On

When you nock a request, the default behavior is that Nock will only intercept a single request. After the single request, that interceptor is removed from the stack so any further requests creates an error. Nock provides two methods to change this behavior.

.times(), which allows a single interceptor to act on n requests back to back.

.persist(), which will stop an interceptor from removing itself from the stack after being used. Therefore being able to be reused over and over for any request.

If you're writing a test that covers the fault tolerance of your code, say with Axios config that is setup to retry four times. You could create one Nock that returns a 500 three times then returns a 200 on the four attempt. Or mix and match as needed.

nock(serverBaseUrl)
  .put('/exclusion-lists', { some: 'data' })
  .reply(500, { error: 'Some error' })
  .times(3)
  .reply(200, { message: 'Success!' });