如何为调用外部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
相关产品推荐
相关产品推荐

