CodePark/ questions / Testing NestJS apps
START PRACTICINGPRACTICE
Backend question · Backend testing › Testing NestJS apps

How do you unit test this RolesGuard without starting an HTTP app, and what still needs an e2e test?

@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:

  • getHandler and getClass return 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.