NestJS中间件Axios HttpService单元测试:异常路径未触发ForbiddenException
NestJS中间件异常路径测试问题
我的NestJS中间件会从微服务端点获取JSON文档并附加到请求中。正常路径的Jest测试能正常通过,但异常路径测试无法触发ForbiddenException,导致next()被调用。实际运行时,中间件在获取文档失败时会阻止请求,求解决测试问题。
中间件代码
import { ForbiddenException, Injectable, NestMiddleware } from '@nestjs/common' import { HttpService } from '@nestjs/axios' import { NextFunction, Request, Response } from 'express' import { catchError, firstValueFrom } from 'rxjs' import { LoggerService } from '../logger.service' @Injectable() export class MyMiddleware implements NestMiddleware { constructor( private logger: LoggerService, private httpService: HttpService, ) {} async use(req: Request, res: Response, next: NextFunction) { const response = await firstValueFrom( this.httpService .get('https://myservice/document-endpoint', { headers: { Cookie: req.headers['cookie'], }, }) .pipe( catchError((error: Error) => { this.logger.error(error.message); throw new ForbiddenException() }), ), ) req.document = response.data.value next() } }
测试代码
import { MyMiddleware } from './mymiddleware' import { NextFunction, Request, Response } from 'express' import { LoggerService } from '../logger/logger.service' import { of } from 'rxjs' import { createMock } from '@golevelup/nestjs-testing' import { HttpService } from '@nestjs/axios' import { AxiosResponse } from 'axios' describe('Authorization middleware', () => { let middleware: MyMiddleware let mockRequest: Partial<Request> let mockResponse: Partial<Response> let nextFunction: NextFunction = jest.fn() let mockHttpService = createMock<HttpService>() mockRequest = { headers: { cookie: 'idToken=asdasdasd' } } beforeEach(async () => { mockResponse = { json: jest.fn(), } middleware = new MyMiddleware(new LoggerService(), mockHttpService) }) test('bad path', async () => { jest.clearAllMocks() const mockDocumentResponse: AxiosResponse = { status: 404, statusText: '', headers: {}, config: {}, data: { error: 'Not Found' } } const httpSpy = jest.spyOn(mockHttpService, 'get') .mockReturnValue(of(mockDocumentResponse)) await middleware.use(mockRequest as Request, mockResponse as Response, nextFunction) expect(nextFunction).toBeCalledTimes(0) }) test('good path', async () => { jest.clearAllMocks() const mockDocumentResponse: AxiosResponse = { status: 200, statusText: '', headers: {}, config: {}, data: { value: 'example document', } } jest.spyOn(mockHttpService, 'get').mockImplementationOnce(() => of(mockDocumentResponse)); await middleware.use(mockRequest as Request, mockResponse as Response, nextFunction) expect(nextFunction).toBeCalledTimes(1) }) })
问题原因及解决方法
问题根源
当前异常测试用例中,仅返回了状态码404的AxiosResponse,但HttpService.get()只有在请求抛出错误时才会进入catchError管道。直接返回4xx响应属于正常响应流,不会触发错误处理逻辑,因此中间件不会抛出ForbiddenException,反而会执行到next()。
解决步骤
- 模拟HttpService抛出错误:让
get方法返回一个抛出错误的Observable,而非正常响应。 - 捕获并验证异常:在测试中捕获中间件抛出的
ForbiddenException,同时验证next()未被调用。
修改后的异常测试用例:
// 先导入throwError import { of, throwError } from 'rxjs' // 导入ForbiddenException import { ForbiddenException } from '@nestjs/common' test('bad path', async () => { jest.clearAllMocks() // 模拟HttpService抛出请求失败的错误 const httpSpy = jest.spyOn(mockHttpService, 'get') .mockReturnValue(throwError(() => new Error('Request failed with status code 404'))) // 验证中间件抛出ForbiddenException await expect(middleware.use(mockRequest as Request, mockResponse as Response, nextFunction)) .rejects.toThrow(ForbiddenException) // 验证next()未被调用 expect(nextFunction).toBeCalledTimes(0) })
补充说明
throwError会创建一个立即抛出错误的Observable,触发catchError管道,进而让中间件抛出ForbiddenException。- 由于中间件是异步函数,需用
await expect(...).rejects.toThrow(...)来捕获并验证异常。 - Axios默认只会将状态码≥500的响应视为错误,若要让4xx响应也触发错误,需配置
validateStatus选项,但在测试中直接模拟错误流更高效。
内容的提问来源于stack exchange,提问作者jpmc
相关产品推荐
相关产品推荐

