Search code examples
javascriptunit-testingjestjsnestjstypeorm

test case for roles guard file in nestjs


I am not sure I how to write unit test case file for guard in nestjs. I have below Role.guard.ts file. I have to create Role.guard.spec.ts file. can somebody help please?

import { Injectable, CanActivate, ExecutionContext, Logger } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
import { Role } from './role.enum';

@Injectable()
export class RolesGuard implements CanActivate {
    constructor(private reflector: Reflector) {}

    canActivate(context: ExecutionContext): boolean {
        const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
            context.getHandler(),
            context.getClass(),
        ]);

        const { user } = context.switchToHttp().getRequest();

        if (!user.role) {
            Logger.error('User does not have a role set');
            return false;
        }

        if (user.role === Role.Admin) {
            return true;
        }

        if (!Array.isArray(requiredRoles) || !requiredRoles.length) {
            // No @Roles() decorator set, deny access as not admin
            return false;
        }

        if (requiredRoles.includes(Role.All)) {
            return true;
        }

        return requiredRoles.includes(user.role);
    }
}

I wrote below code but coverage issue is coming.

import { createMock } from '@golevelup/ts-jest';
import { ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { RolesGuard } from "./roles.guard";

describe('RolesGuard', () => {
  let guard: RolesGuard;
  let reflector: Reflector;

  beforeEach(() => {
    reflector = new Reflector();
    guard = new RolesGuard(reflector);
  });

  it('should be defined', () => {
    expect(guard).toBeDefined();
  });

  it('should return false if user does not exist', () => {
    reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
    const context = createMock<ExecutionContext>();
    const canActivate = guard.canActivate(context);
    expect(canActivate).toBe(false); 
  })
})

below lines are not getting covered.

if (user.role === Role.Admin) {
        return true;
    }

    if (!Array.isArray(requiredRoles) || !requiredRoles.length) {
        // No @Roles() decorator set, deny access as not admin
        return false;
    }

    if (requiredRoles.includes(Role.All)) {
        return true;
    }

    return requiredRoles.includes(user.role);

Edit 1:-

Below test cases are covering my some part of code.

     it('should return true if user  exist', () => {
        reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
        const context = createMock<ExecutionContext>({
          switchToHttp: () => ({
            getRequest: () => ({
              user: {
                role:'admin'
              }
            }),
          }),
        });
    
        const canActivate = guard.canActivate(context);
        expect(canActivate).toBe(true); 
      })
    
      it('should return false if user does not exist', () => {
        reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
        const context = createMock<ExecutionContext>({
          switchToHttp: () => ({
            getRequest: () => ({
              user: {
                role:'user'
              }
            }),
          }),
        });
    
        const canActivate = guard.canActivate(context);
        expect(canActivate).toBe(false); 
      })

But below code still not getting covered.

     if (requiredRoles.includes(Role.All)) {
        return true;
    }

    return requiredRoles.includes(user.role);
}

Can sombody help me on the same?


Solution

  • Looks like you need to be able to craft the payload appropriately such that your code is hit. There are a variety of ways to do this but, in a "Nest"-y way, we can try something like what the docs tell us. Notice in that link that the provided testing utilities make this a lot easier to mock.

    import { createMock } from '@golevelup/ts-jest';
    import { ExecutionContext } from "@nestjs/common";
    import { Reflector } from "@nestjs/core";
    import { RolesGuard } from "./roles.guard";
    
    describe('RolesGuard', () => {
      let guard: RolesGuard;
      let reflector: Reflector
    
      beforeEach(async () => {
        reflector = new Reflector();
        guard = new RolesGuard(reflector);
      });
    
      it('should be defined', () => {
        expect(guard).toBeDefined();
      });
    
      it('should return false if user does not exist', () => {
        reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
        const context = createMock<ExecutionContext>();
        const canActivate = guard.canActivate(context);
        expect(canActivate).toBe(false); 
      })
    
      it('should return false if user does exist', () => {
        reflector.getAllAndOverride = jest.fn().mockReturnValue(true);
    
        // Mock the "class" type of this so we can get what we want.
        // We want to tell it to return an object where role is defined.
        const context = createMock<ExecutionContext>({
          switchToHttp: () => ({
            getRequest: () => ({
              user: { role: { /* enter your data here */ }
            }),
          }),
        });
    
        const canActivate = guard.canActivate(context);
        expect(canActivate).toBe(false); 
      })
    })
    

    From here your context is successfully hydrated with whatever you want and the second test should start showing up as covering your other code branches. You can now edit the role attribute to look however you want. Notice also in the above beforeEach call you should be able to switch to using testing modules instead. This is not exhaustive though, you'll likely need to add additional test cases to cover your other branches. If you follow as I've done here, that should be relatively trivial.

    Does what I've done here make sense? By crafting a custom payload to the object, the roles attribute is present, allowing the test to evaluate other cases. I got my override from the createMock function directly from the docs for golevelup.