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

如何在Jest中Mock NestJS的AdminGuard?测试异常排查

问题分析与解决方案

核心错误

你的测试用例直接调用了usersService.findOne('userid'),这相当于绕过了控制器和守卫的校验逻辑,直接调用服务层方法。因此,你对AdminGuard的mock完全不会生效,测试根本没有触发守卫的校验逻辑,自然无法验证守卫的行为。

正确的测试方式

要验证守卫逻辑,你需要通过模拟HTTP请求触发控制器方法,让Nest框架自动执行守卫的校验。推荐使用supertest来模拟HTTP请求,或者单独测试守卫类的canActivate方法。


方案1:使用supertest模拟HTTP请求(推荐)

这种方式最接近真实请求场景,能完整验证守卫+控制器的逻辑。

步骤1:确保测试模块正确初始化应用

import * as request from 'supertest';
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { AdminGuard } from './admin.guard';

const mockUsersService = {
  findOne: jest.fn().mockResolvedValue({
    id: 'userid',
    name: 'User One',
    email: 'userone@example.com'
  })
};

const mockAdminGuard = {
  canActivate: jest.fn(() => true),
};

describe('UsersController', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [UsersController],
      providers: [
        { provide: UsersService, useValue: mockUsersService },
        { provide: AdminGuard, useValue: mockAdminGuard },
      ],
    }).compile();

    app = module.createNestApplication();
    await app.init(); // 初始化应用
  });

  afterEach(async () => {
    await app.close(); // 清理应用
  });

  // 正确的测试用例
  describe('getUser', () => {
    it('should return 403 when request is not from admin', async () => {
      // 让守卫返回false
      mockAdminGuard.canActivate.mockImplementationOnce(() => false);

      // 模拟GET请求到控制器的路由
      const response = await request(app.getHttpServer())
        .get('/users/userid') // 对应控制器的@Get(':id')路由
        .send();

      // 断言响应状态码为403
      expect(response.status).toBe(403);
      // 验证守卫的canActivate方法被调用过
      expect(mockAdminGuard.canActivate).toHaveBeenCalled();
    });

    it('should return user data when request is from admin', async () => {
      // 守卫默认返回true
      const response = await request(app.getHttpServer())
        .get('/users/userid')
        .send();

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        id: 'userid',
        name: 'User One',
        email: 'userone@example.com'
      });
      expect(mockUsersService.findOne).toHaveBeenCalledWith('userid');
    });
  });
});

方案2:单独测试AdminGuard类

如果你只想验证守卫自身的业务逻辑,可以直接实例化守卫并调用canActivate方法,模拟ExecutionContext参数:

import { AdminGuard } from './admin.guard';
import { Reflector } from '@nestjs/core';
import { ExecutionContext } from '@nestjs/common';

// 模拟Reflector
const mockReflector = {} as Reflector;

describe('AdminGuard', () => {
  let guard: AdminGuard;

  beforeEach(() => {
    guard = new AdminGuard(mockReflector);
  });

  it('should return false if user is not admin', async () => {
    // 模拟请求对象,包含user.sub
    const mockContext = {
      switchToHttp: jest.fn().mockReturnValue({
        getRequest: jest.fn().mockReturnValue({
          user: { sub: 'non-admin-user-id' }
        })
      })
    } as unknown as ExecutionContext;

    // 模拟User.findOne返回非管理员用户
    jest.spyOn(User, 'findOne').mockResolvedValue({
      id: 'non-admin-user-id',
      role: 'user' // 非admin角色
    });

    const result = await guard.canActivate(mockContext);
    expect(result).toBe(false);
    expect(User.findOne).toHaveBeenCalledWith({ where: { id: 'non-admin-user-id' } });
  });

  it('should return true if user is admin', async () => {
    const mockContext = {
      switchToHttp: jest.fn().mockReturnValue({
        getRequest: jest.fn().mockReturnValue({
          user: { sub: 'admin-user-id' }
        })
      })
    } as unknown as ExecutionContext;

    jest.spyOn(User, 'findOne').mockResolvedValue({
      id: 'admin-user-id',
      role: 'admin'
    });

    const result = await guard.canActivate(mockContext);
    expect(result).toBe(true);
  });
});

额外注意点

  1. 你的原测试断言expect(response).toBe(123123123)完全不符合逻辑,因为usersService.findOne的mock返回的是用户对象,所以实际收到的是用户数据,断言自然失败。修正后需要根据测试场景设置正确的断言(比如状态码、响应体)。
  2. 确保mockUsersService的方法返回值符合预期,避免干扰测试结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 03:35:18