@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(ctx: ExecutionContext) {
const roles = this.reflector.getAllAndOverride<string[]>(
'roles', [ctx.getHandler(), ctx.getClass()]);
if (!roles) return true;
const { user } = ctx.switchToHttp().getRequest();
return roles.some((r) => user?.roles?.includes(r));
}
}
Create the guard yourself with new, and give it a fake request context. A guard is a plain class, so you don't need an HTTP app: const reflector = new Reflector(); const guard = new RolesGuard(reflector);.
The Reflector reads the roles that @Roles() attached to the route. This attached data is called metadata. Control it in the test with jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(['admin']).
The ExecutionContext describes the current request. Build a minimal one with only what the guard uses:
getHandlerandgetClassreturn dummy functions.switchToHttp()returns{ getRequest: () => ({ user: { roles: ['user'] } }) }.
Cast it with as unknown as ExecutionContext. Or generate it with createMock<ExecutionContext>() from @golevelup/ts-jest.
Cover the cases as a table with it.each:
- No metadata: the guard allows the request.
- A matching role: the guard allows it.
- A missing role: the guard denies it.
- No user: the guard denies it.
Still keep one e2e (end-to-end) test. It proves the guard is actually bound to the route. A unit test can't catch a missing @UseGuards() or @Roles().
- Create the guard directly with a real or spied Reflector.
- Build a minimal ExecutionContext by hand, or use createMock.
- Test a table of cases: no metadata, matching role, missing role and no user.
- An e2e test still checks that the guard is wired to the route.
- NestJS guards (https://docs.nestjs.com/guards)docs.nestjs.com
- NestJS testing (https://docs.nestjs.com/fundamentals/testing)docs.nestjs.com