如何在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); }); });
额外注意点
- 你的原测试断言
expect(response).toBe(123123123)完全不符合逻辑,因为usersService.findOne的mock返回的是用户对象,所以实际收到的是用户数据,断言自然失败。修正后需要根据测试场景设置正确的断言(比如状态码、响应体)。 - 确保
mockUsersService的方法返回值符合预期,避免干扰测试结果。
内容的提问来源于stack exchange,提问作者Artem
相关产品推荐
相关产品推荐

