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

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()。

解决步骤

  1. 模拟HttpService抛出错误:让get方法返回一个抛出错误的Observable,而非正常响应。
  2. 捕获并验证异常:在测试中捕获中间件抛出的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 19:40:27