使用axios-retry时nock无法拦截后续请求的解决方法
Nock与axios-retry协同工作的问题与解决方案
问题概述
- 正常请求场景下,
nock可以正常拦截并模拟API响应 - 触发
axios-retry的重试逻辑时,首次重试请求就会触发Nock匹配失败,日志提示无匹配的请求规则
相关代码与测试信息
客户端请求代码
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) => { // 其他逻辑已省略 return Number(error.response.status) >= 500; }, }); }
待测试的业务逻辑
export const upsertExclusionList = async () => { const response: ApiClientResult = sendRequest({ someParams }); if (response.data) { return response.data; } else { throw Error('No data'); } }
正常工作的测试用例
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' }); });
触发重试的失败测试用例
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); // 也曾尝试: // await expect(upsertExclusionList({ someParams }).rejects.toEqual('No data'); });
实际报错信息
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)".
依赖版本
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"
解决方案
Nock支持重试场景,核心问题是默认每个拦截器仅匹配一次请求,而axios-retry会发起多次重复请求,导致后续请求无匹配规则。以下是具体配置步骤:
1. 指定Nock拦截器的匹配次数
在测试用例中,给Nock拦截器添加times(n)方法,n为总请求次数(初始请求1次 + 重试次数)。比如你设置了retries: 3,则总请求数是4次:
it('handles a 500 response from the API', async () => { // 匹配4次请求(1次初始 + 3次重试) nock(serverBaseUrl) .put('/exclusion-lists', { some: 'data' }) .query({ callerId: 'Tester', role: 'Tester' }) .times(4) .reply(500, { error: 'Some error' }); // 验证业务逻辑抛出预期错误 await expect(upsertExclusionList({ someParams })).rejects.toThrow('No data'); // 验证所有请求都被Nock匹配 expect(nock.isDone()).toBe(true); });
如果不需要精确控制次数,也可以用persist()让拦截器持续匹配直到测试结束,但times()更适合明确重试次数的场景:
nock(serverBaseUrl) .put('/exclusion-lists', { some: 'data' }) .query({ callerId: 'Tester', role: 'Tester' }) .persist() .reply(500, { error: 'Some error' });
2. 修正测试断言逻辑
原测试用例中的toBeCalledTimes(4)是错误用法,该方法用于验证函数被调用的次数,而非请求次数。正确的做法是:
- 验证业务逻辑是否抛出预期错误
- 用
nock.isDone()确认所有请求都被Nock拦截匹配
3. 确保请求参数完全匹配
如果仍出现匹配失败,检查请求的headers、body、URL是否与Nock规则完全一致:
- 比如报错中出现的
user-agent、accept-encoding等headers,Nock默认会严格匹配,可通过matchHeader宽松匹配:nock(serverBaseUrl) .put('/exclusion-lists', { some: 'data' }) .query({ callerId: 'Tester', role: 'Tester' }) .matchHeader('user-agent', /.*/) // 匹配任意user-agent .matchHeader('accept-encoding', /.*/) .times(4) .reply(500, { error: 'Some error' }); - 确保请求body的序列化格式与Nock预期一致(比如是否有多余的空格、字段顺序问题)
内容的提问来源于stack exchange,提问作者Juha Untinen
相关产品推荐
相关产品推荐

