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

如何为NestJS中的参数装饰器编写单元测试?

自定义NestJS装饰器的单元测试方案

一、直接测试装饰器本身

你的AuthUser装饰器基于createParamDecorator实现,核心逻辑是从请求上下文的args[0]中提取principal字段。测试时只需模拟对应的上下文参数,验证返回值是否符合预期:

import { AuthUser } from './users.decorator';
import { ExecutionContext } from '@nestjs/common';

describe('AuthUser Decorator', () => {
  it('应正确返回请求中的principal对象', () => {
    // 模拟测试用的用户信息
    const mockPrincipal = { id: 1, username: 'test-user' };
    // 模拟ExecutionContext,构造符合装饰器读取逻辑的结构
    const mockContext = {
      args: [{ principal: mockPrincipal }]
    } as unknown as ExecutionContext;

    // 调用装饰器,传入参数'user'和模拟上下文
    const result = AuthUser('user', mockContext);

    expect(result).toEqual(mockPrincipal);
  });
});

二、结合控制器测试(验证真实场景)

在控制器中使用装饰器时,需要结合守卫、服务一起测试,确保装饰器能正确将用户对象注入到方法参数中:

测试步骤:

  1. 模拟LogbooksService和UsersGuard,跳过真实的认证逻辑
  2. 在守卫的canActivate方法中,给请求对象添加模拟的principal
  3. 调用控制器的createLogbook方法,验证服务是否接收到正确的用户参数
import { Test } from '@nestjs/testing';
import { LogbooksController } from './logbooks.controller';
import { LogbooksService } from './logbooks.service';
import { UsersGuard } from '../users/users.guard';
import { CreateLogbookDto } from './dto/CreateLogbook.dto';
import { ExecutionContext } from '@nestjs/common';

describe('LogbooksController', () => {
  let controller: LogbooksController;
  let logbooksService: jest.Mocked<LogbooksService>;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [LogbooksController],
      providers: [
        {
          provide: LogbooksService,
          useValue: {
            createLogbook: jest.fn().mockResolvedValue({ id: 1, ...new CreateLogbookDto() })
          }
        }
      ]
    })
    // 替换UsersGuard为模拟实现,添加测试用的principal
    .overrideGuard(UsersGuard)
    .useValue({
      canActivate: jest.fn((context: ExecutionContext) => {
        const req = context.switchToHttp().getRequest();
        req.principal = { id: 1, username: 'test-user' };
        return true;
      })
    })
    .compile();

    controller = moduleRef.get<LogbooksController>(LogbooksController);
    logbooksService = moduleRef.get(LogbooksService);
  });

  it('创建日志时应正确传递认证用户', async () => {
    const createDto: CreateLogbookDto = { title: '测试日志', content: '测试内容' };
    const mockUser = { id: 1, username: 'test-user' };

    await controller.createLogbook(createDto, mockUser);

    // 验证服务方法被调用时传入了正确的用户对象
    expect(logbooksService.createLogbook).toHaveBeenCalledWith(createDto, mockUser);
  });
});

可选优化:让装饰器适配不同HTTP适配器

当前装饰器直接读取req.args[0],仅适配Express场景。如果需要兼容Fastify,建议修改为NestJS标准的上下文读取方式:

// 优化后的AuthUser装饰器
import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const AuthUser = createParamDecorator((data: string, context: ExecutionContext) => {
  const request = context.switchToHttp().getRequest();
  return request.principal;
});

这种写法更通用,后续测试也可以通过context.switchToHttp().getRequest()统一设置请求参数,无需关注底层框架的args结构。

内容的提问来源于stack exchange,提问作者Xeyos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 10:25:53