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

如何为调用外部API的NestJS Controller与Service编写正确单元测试

优化说明与测试代码

你现有的控制器测试思路基本正确,以下是可优化的点和完整的测试代码:

现有代码的问题点

  • 控制器测试引入了真实的HttpModule和PhotosService实例,单元测试的核心是隔离外部依赖,不需要加载真实的Http模块,直接mock掉PhotosService即可,测试运行速度更快,也不会因为外部依赖波动影响测试结果。
  • 测试用例描述错误:should return a user和当前获取照片列表的接口功能不匹配,需要修正。
  • 缺少PhotosService的单元测试,这部分是调用外部API的核心逻辑,需要单独测试验证。

修正后的控制器测试代码(photos.controller.spec.ts)

import { Test, TestingModule } from '@nestjs/testing';
import { AxiosResponse } from 'axios';
import { PhotosController } from './photos.controller';
import { PhotosService } from './photos.service';

describe('PhotosController', () => {
  let photosController: PhotosController;
  // 声明mock的photosService
  let mockPhotosService: Partial<jest.Mocked<PhotosService>>;

  beforeEach(async () => {
    // 提前定义mock的service方法
    mockPhotosService = {
      getPhotos: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      controllers: [PhotosController],
      // 用自定义provider替换真实的PhotosService
      providers: [
        {
          provide: PhotosService,
          useValue: mockPhotosService,
        }
      ],
      // 不需要导入HttpModule了
    }).compile();

    photosController = module.get<PhotosController>(PhotosController);
  });

  it('should be defined', () => {
    expect(photosController).toBeDefined();
  });

  describe('getPhotos', () => {
    it('should return photos list from service', async () => {
      const mockData = [{ id: 1, title: 'test photo', url: 'test.png' }];
      const mockResponse: AxiosResponse<any> = {
        data: mockData,
        headers: {},
        config: {},
        status: 200,
        statusText: 'OK',
      };
      // 给mock方法设置返回值
      mockPhotosService.getPhotos.mockResolvedValue(mockResponse);

      const result = await photosController.getPhotos();
      // 验证返回值正确
      expect(result).toEqual(mockResponse);
      // 验证service的方法被正确调用
      expect(mockPhotosService.getPhotos).toHaveBeenCalledTimes(1);
    });
  });
});

PhotosService 单元测试代码(photos.service.spec.ts)

import { Test, TestingModule } from '@nestjs/testing';
import { HttpService } from '@nestjs/axios';
import { PhotosService } from './photos.service';
import { AxiosResponse } from 'axios';

describe('PhotosService', () => {
  let photosService: PhotosService;
  let mockHttpService: Partial<jest.Mocked<HttpService>>;

  beforeEach(async () => {
    // mock HttpService的axiosRef对象
    mockHttpService = {
      axiosRef: {
        get: jest.fn(),
      } as any,
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PhotosService,
        {
          provide: HttpService,
          useValue: mockHttpService,
        },
      ],
    }).compile();

    photosService = module.get<PhotosService>(PhotosService);
  });

  it('should be defined', () => {
    expect(photosService).toBeDefined();
  });

  describe('getPhotos', () => {
    it('should call correct external API and return data', async () => {
      const mockData = [{ id: 1, title: 'test photo', url: 'test.png' }];
      const mockResponse: AxiosResponse<any> = {
        data: mockData,
        headers: {},
        config: {},
        status: 200,
        statusText: 'OK',
      };
      mockHttpService.axiosRef.get.mockResolvedValue(mockResponse);

      const result = await photosService.getPhotos();
      // 验证返回值正确
      expect(result).toEqual(mockResponse);
      // 验证调用了正确的API地址
      expect(mockHttpService.axiosRef.get).toHaveBeenCalledWith(
        'https://jsonplaceholder.typicode.com/photos'
      );
      expect(mockHttpService.axiosRef.get).toHaveBeenCalledTimes(1);
    });
  });
});

内容的提问来源于stack exchange,提问作者Franavi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 13:54:03