NestJS测试中使用HttpService.get捕获Axios错误响应如何避免超时?
问题描述
原有代码可通过playlistID获取YouTube播放列表最新上传视频,但修改代码以捕获无效playlistID的错误响应后,测试用例超时失败。
原测试代码(get_latest_upload.service.spec.ts)
import { Test, TestingModule } from '@nestjs/testing'; import { GetLatestUploadService } from './get_latest_upload.service'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; describe('GetLatestUploadService', () => { let service: GetLatestUploadService; let httpService: jest.Mocked<HttpService>; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [], providers: [ { provide: HttpService, useValue: createMock<HttpService>() }, ConfigService ], controllers: [ GetLatestUploadService ], }).compile(); service = module.get<GetLatestUploadService>(GetLatestUploadService); httpService = module.get(HttpService); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('getLatestUpload(id)', () => { it('sholud return youtube video id', async () => { const playlistID = "PLcGkkXtask_eafKUbnGPBuCsLt2LN8SQe"; const testData = require(`../../test/playlists/${playlistID}.json`); const testID = testData.items[0].snippet.playlistId; const videoID = testData.items[0].snippet.resourceId.videoId; httpService.axiosRef.mockResolvedValueOnce({ data: testData, headers: {}, config: { url: '' }, status: 200, statusText: '', }); const getLatestUpload = service.getLatestUpload(testID); await expect(getLatestUpload).resolves.toBe(videoID); }); }); });
原服务代码(get_latest_upload.service.ts)
import { Inject, Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom, catchError } from 'rxjs'; import { AxiosError } from 'axios'; import { AxiosResponse } from 'axios'; @Injectable() export class GetLatestUploadService { @Inject(ConfigService) public config: ConfigService; private readonly logger = new Logger(GetLatestUploadService.name); constructor(private readonly httpService: HttpService) {} async getLatestUpload(playlistID): Promise<string> { const apiKey: string = this.config.get('YOUTUBE_API_KEY'); const part = "snippet%2C+id"; const url = `https://www.googleapis.com/youtube/v3/playlistItems?part=${part}&playlistId=${playlistID}&key=${apiKey}&order=date&maxResults=1`; const { data } = await this.httpService.axiosRef({ url: url, method: `GET`, }); const videoId = data.items[0].snippet.resourceId.videoId; return videoId; } }
修改后的服务代码(添加错误捕获)
import { Inject, Injectable, Logger, HttpException, HttpStatus } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { HttpService } from '@nestjs/axios'; import { firstValueFrom, catchError } from 'rxjs'; import { AxiosError } from 'axios'; @Injectable() export class GetLatestUploadService { @Inject(ConfigService) public config: ConfigService; private readonly logger = new Logger(GetLatestUploadService.name); constructor(private readonly httpService: HttpService) {} async getLatestUpload(playlistID: string): Promise<string> { const apiKey: string = this.config.get('YOUTUBE_API_KEY'); const part = "snippet%2C+id"; const url = `https://www.googleapis.com/youtube/v3/playlistItems?part=${part}&playlistId=${playlistID}&key=${apiKey}&order=date&maxResults=1`; const { data } = await firstValueFrom( this.httpService.get(url).pipe( catchError((error: AxiosError) => { this.logger.error(error.response?.data || error.message); throw new HttpException( error.response?.data?.error?.message || 'Failed to fetch playlist', error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR, ); }), ), ); return data.items[0].snippet.resourceId.videoId; } }
测试超时原因及修复
测试超时是因为服务代码改用了httpService.get(),但测试中依然mock的是httpService.axiosRef,导致mock未命中,httpService.get()返回的Observable一直处于未完成状态,最终触发超时。
修改测试代码,改为mockhttpService.get()方法:
import { Test, TestingModule } from '@nestjs/testing'; import { GetLatestUploadService } from './get_latest_upload.service'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from '@nestjs/config'; import { createMock } from '@golevelup/ts-jest'; import { of } from 'rxjs'; import { HttpException } from '@nestjs/common'; import { AxiosError } from 'axios'; describe('GetLatestUploadService', () => { let service: GetLatestUploadService; let httpService: jest.Mocked<HttpService>; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ GetLatestUploadService, { provide: HttpService, useValue: createMock<HttpService>() }, ConfigService ], }).compile(); service = module.get<GetLatestUploadService>(GetLatestUploadService); httpService = module.get(HttpService); }); it('should be defined', () => { expect(service).toBeDefined(); }); describe('getLatestUpload(id)', () => { it('should return youtube video id', async () => { const playlistID = "PLcGkkXtask_eafKUbnGPBuCsLt2LN8SQe"; const testData = require(`../../test/playlists/${playlistID}.json`); const testID = testData.items[0].snippet.playlistId; const videoID = testData.items[0].snippet.resourceId.videoId; // 改为mock httpService.get,返回Observable httpService.get.mockReturnValueOnce(of({ data: testData, headers: {}, config: { url: '' }, status: 200, statusText: '', })); const result = await service.getLatestUpload(testID); expect(result).toBe(videoID); }); it('should throw error when playlist ID is invalid', async () => { const invalidPlaylistID = 'invalid-id'; const mockErrorResponse = { response: { status: 404, data: { error: { message: "The playlist identified with the request's <code>playlistId</code> parameter cannot be found." } } } } as AxiosError; // mock httpService.get抛出错误 httpService.get.mockImplementationOnce(() => { throw mockErrorResponse; }); await expect(service.getLatestUpload(invalidPlaylistID)).rejects.toThrow(HttpException); }); }); });
关键修改点
- 测试mock对象对齐:将
httpService.axiosRef.mockResolvedValueOnce改为httpService.get.mockReturnValueOnce,并返回of()包装的Observable(Nest的HttpService.get返回Observable)。 - 错误处理优化:将抛出字符串错误改为抛出Nest标准的
HttpException,符合框架异常处理规范,便于全局异常过滤器统一处理。 - 添加错误场景测试:新增测试用例验证无效playlistID时的错误抛出逻辑。
内容的提问来源于stack exchange,提问作者OB7DEV
相关产品推荐
相关产品推荐

