You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何测试含嵌套Axios请求的异步服务?Jest测试问题求助

解决思路:Mock内部依赖,专注核心逻辑测试

你的问题核心在于:persons.get里先调用了异步的places.get,但测试中没有处理这个依赖的Promise,导致代码卡在等待places.get响应的阶段,根本没走到personsAgent.getAll的调用步骤。要解决这个问题,我们需要先Mock掉places服务的get方法,让它直接返回预设的模拟数据,这样代码才能继续执行后续逻辑。

下面提供两种可行的方案,都是基于Jest本身的Mock能力,不需要额外工具:

方案一:Mock整个places服务模块

直接用jest.mock替换掉整个places模块,让它的get方法返回我们需要的模拟响应:

import mockAxios from 'jest-mock-axios';
import persons from './persons';
import places from './places';

// 全局Mock places服务,让get方法默认返回空的地点数据
jest.mock('./places', () => ({
  get: jest.fn().mockResolvedValue({
    data: { results: [] },
    status: 200
  })
}));

afterEach(() => {
  mockAxios.reset();
});

it('returns Persons data from API with mapped place info', async () => {
  // 1. 自定义本次测试的places响应数据(匹配后续person的过滤逻辑)
  const mockPlacesData = {
    data: { results: [{ id: 123, name: 'New York' }] },
    status: 200
  };
  places.get.mockResolvedValueOnce(mockPlacesData);

  // 2. 调用待测试的persons.get方法
  const requestPromise = persons.get({ search: '', limit: 10, offset: 0 });

  // 3. 断言places.get被正确调用
  expect(places.get).toHaveBeenCalledWith({ search: '', limit: 1000, offset: 0 });

  // 4. 断言personsAgent.getAll对应的axios请求被触发
  expect(mockAxios.get).toHaveBeenCalledWith('/persons/', {
    params: { search: null, limit: 10, offset: 0 },
  });

  // 5. 模拟axios返回persons数据(注意要包含匹配place_id的字段)
  const mockPersonsResponse = {
    data: { 
      results: [{ 
        id: 456, 
        first_name: 'Alice', 
        last_name: 'Smith', 
        place_id: 123 // 和上面mock的place id对应
      }] 
    },
    status: 200
  };
  mockAxios.mockResponse(mockPersonsResponse);

  // 6. 等待Promise完成,断言最终返回结果
  const finalResult = await requestPromise;
  expect(finalResult).toEqual({
    data: {
      ...mockPersonsResponse.data,
      results: [{
        id: 456,
        name: 'Alice',
        surname: 'Smith',
        place: mockPlacesData.data.results[0]
      }]
    },
    status: 200
  });
});

方案二:用jest.spyOn局部Mockplaces.get方法

如果你不想替换整个places模块,只想Mock它的get方法,可以用jest.spyOn来实现局部Mock:

import mockAxios from 'jest-mock-axios';
import persons from './persons';
import places from './places';

afterEach(() => {
  mockAxios.reset();
  jest.restoreAllMocks(); // 每次测试后恢复被Spy的方法
});

it('returns Persons data from API', async () => {
  // 1. Spy并Mock places.get方法
  const placesGetSpy = jest.spyOn(places, 'get').mockResolvedValue({
    data: { results: [{ id: 789, name: 'London' }] },
    status: 200
  });

  // 2. 调用persons.get
  const requestPromise = persons.get({ search: 'test', limit: 5, offset: 0 });

  // 3. 验证places.get的调用参数
  expect(placesGetSpy).toHaveBeenCalledWith({ search: '', limit: 1000, offset: 0 });

  // 4. 验证persons的axios请求参数(此时search有值,不会是null)
  expect(mockAxios.get).toHaveBeenCalledWith('/persons/', {
    params: { search: 'test', limit: 5, offset: 0 },
  });

  // 5. 模拟axios返回persons数据
  const mockPersonsResponse = {
    data: { results: [{ id: 101, first_name: 'Bob', last_name: 'Brown', place_id: 789 }] },
    status: 200
  };
  mockAxios.mockResponse(mockPersonsResponse);

  // 6. 断言最终结果
  const finalResult = await requestPromise;
  expect(finalResult.data.results[0].place.name).toBe('London');
  expect(finalResult.status).toBe(200);
});

关键说明

  1. 为什么之前的测试失败?
    因为places.get是异步函数,测试中没有处理它的Promise,代码会一直等待它的响应,永远走不到personsAgent.getAll的调用步骤,所以你只能看到/places/的axios请求,看不到/persons/的。

  2. Mock内部依赖的优势
    我们不需要关心places.get的真实实现,只需要预设它的返回值,就能专注测试persons.get的核心逻辑:参数处理、数据排序映射、axios调用等。

  3. 异步测试的最佳实践
    尽量使用await等待Promise完成,而不是用then/catch回调,这样代码更清晰,断言也更准确,避免出现回调未执行的问题。

内容的提问来源于stack exchange,提问作者Bartek Ł.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 21:52:31