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

NestJS拦截器单元测试:如何验证响应头设置是否正确

Nest拦截器单元测试响应头校验方案

业务场景

编写的拦截器会拦截GET请求,并根据特定条件向响应添加对应响应头。

控制器代码

@UseInterceptors(ServiceHeaderInterceptor)
@Get('users')
public async users(
  @Query('id') Id: number,
  @Query('name') Name: string,
){
  const setFlags: MetaFlags = {setCacheHeader: false, setServiceHeader: false};
  const data = await this.service.getUsers(id, name, setFlags);
  return data;
}

拦截器代码

注意:原拦截器存在变量名笔误,获取响应对象时定义的变量名为req,后续调用setHeader时用的是res,运行会报未定义错误,需先修正为const res = context.switchToHttp().getResponse();

@Injectable()
export class HeaderInterceptor implements NestInterceptor {
  public intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Observable<any> {
    return next.handle().pipe(
       map((data: { data: DataType, header: 'cache' | 'database' }) => {
        const res = context.switchToHttp().getResponse();
         if (data.header === 'database') {
          res.setHeader('x-api-key', 'pretty secure');
        } else {
          res.setHeader(xyz, value);
        }
        return data.data;
      }),
    );
  }
}

原有测试代码

const users = [
        {
            'id': 1,
            'name': 'Adam',
        },
        {
            'id': 2,
            'name': 'Evan',
        },
    ]

const executionContext: any = {
        switchToHttp: jest.fn().mockReturnThis(),
        getResponse: jest.fn().mockReturnThis(),
        setHeader: jest.fn().mockReturnThis(),
    };
const next = {
        handle: () => of(users),
    };

beforeEach(async () => {
        const module: TestingModule = await Test.createTestingModule({
            providers: [
                {
                    provide: UserService,
                    useValue: mock<UserService>(),
                },
                ServiceHeaderInterceptor,
            ],
        }).compile();

        interceptor = module.get(ServiceHeaderInterceptor);
       
});

it('get users',async () => {
        interceptor.intercept(executionContext, next).subscribe({
                next: (value) => {
                    expect(value).toEqual(users);
                },
                error: (error) => {
                    throw error;
                },
            });

        });
});

存在问题

当前测试仅能校验响应体内容,无法验证拦截器是否正确设置响应头,核心原因有两点:

  • next.handle()返回的数据流直接返回users数组,和拦截器map操作符预期的{data, header}结构不匹配,根本不会进入响应头设置的判断逻辑
  • 响应对象的setHeader方法虽然做了mock,但没有单独保留mock实例,无法追踪调用传参

修正方案

  1. 单独创建可追踪的response mock对象,将setHeader定义为jest mock函数,再注入到executionContext的mock逻辑中
  2. 针对不同的响应头场景,构造对应结构的next.handle()返回值
  3. 在测试断言中,除了校验返回的响应体,额外断言setHeader的调用次数、传入的头名称和头取值

修正后的测试代码示例

// 单独定义mock响应对象,保留setHeader的mock引用方便断言
let mockSetHeader: jest.Mock;
let mockResponse: any;
let executionContext: any;
const testUsers = [
  { id: 1, name: 'Adam' },
  { id: 2, name: 'Evan' },
];

beforeEach(async () => {
  // 每个用例执行前重置mock,避免用例间状态污染
  mockSetHeader = jest.fn().mockReturnThis();
  mockResponse = {
    setHeader: mockSetHeader
  };
  executionContext = {
    switchToHttp: jest.fn().mockReturnThis(),
    getResponse: jest.fn().mockReturnValue(mockResponse),
  };

  const module: TestingModule = await Test.createTestingModule({
    providers: [
      {
        provide: UserService,
        useValue: mock<UserService>(),
      },
      ServiceHeaderInterceptor,
    ],
  }).compile();

  interceptor = module.get(ServiceHeaderInterceptor);
});

// 测试header为database的场景
it('should set x-api-key header when data source is database', (done) => {
  const testNext = {
    handle: () => of({
      data: testUsers,
      header: 'database'
    })
  };

  interceptor.intercept(executionContext, testNext).subscribe({
    next: (value) => {
      // 校验响应体正确
      expect(value).toEqual(testUsers);
      // 校验setHeader被正确调用
      expect(mockSetHeader).toHaveBeenCalledTimes(1);
      expect(mockSetHeader).toHaveBeenCalledWith('x-api-key', 'pretty secure');
      done();
    },
    error: (error) => done(error),
  });
});

// 测试header为cache的场景(对应代码里的else分支,替换成实际的xyz和value值即可)
it('should set cache header when data source is cache', (done) => {
  const testCacheValue = 'max-age=3600';
  const testNext = {
    handle: () => of({
      data: testUsers,
      header: 'cache'
    })
  };

  interceptor.intercept(executionContext, testNext).subscribe({
    next: (value) => {
      expect(value).toEqual(testUsers);
      expect(mockSetHeader).toHaveBeenCalledTimes(1);
      expect(mockSetHeader).toHaveBeenCalledWith(xyz, testCacheValue); // 替换xyz为实际的响应头名称
      done();
    },
    error: (error) => done(error),
  });
});

注意:测试Observable异步逻辑时,用done回调标记异步完成,避免用例提前结束导致断言漏跑


内容的提问来源于stack exchange,提问作者salt-pepper

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 00:36:20