angularunit-testingtestingkarma-jasmine

Unit testing CanActivateFn


In Angular how can I write a unit test case for something that implements CanActivateFn like this?

export const AuthorizedUserClaimGuard: CanActivateFn = (route, state) => {
  const auth = inject(AuthenticationService);
  const config = inject(ConfigurationService);

  if (!auth.authenticated) {
    auth.setState({
      route: state.url,
    });
    auth.logout();
    // tslint:disable-next-line: no-console
    console.info('User is not authenticated, redirecting to the login page.');
  }

  const userClaims = config.authorizedClaims;

  for (const userClaim of userClaims) {
    if (auth.hasPermission(userClaim)) {
      return true;
    }
  }
  return false;
};

I've tried the following code but it doesn't work because AuthorizedUserClaimGuard is not a class. Is there a way to make it work like this?

describe('AuthorizedUserClaimGuard', () => {
  let guard: AuthorizedUserClaimGuard;
  let authentication: AuthenticationService;
  let configuration: ConfigurationService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {
          provide: AuthenticationService,
          useClass: MockAuthenticationService,
        },
      ],
    }).compileComponents();
  }));

  beforeEach(() => {
    authentication = TestBed.get(AuthenticationService);
    configuration = TestBed.get(ConfigurationService);
    configuration.setConfiguration(MOCK_CONFIGURATION);
    guard = new AuthorizedUserClaimGuard(authentication, configuration);
  });

  it('should return true for a logged in user', () => {
    const route = createMockRoute();
    const state = createMockRouteState();
    expect(guard.canActivate(route, state)).toBeTruthy();
  });
}

Solution

  • Yes it is not a class, it is a function. You don't need to declare it. However, as you have injections included, you need to run your function in injection context.

    describe('AuthorizedUserClaimGuard', () => {
      let authentication: AuthenticationService;
    
      beforeEach(async(() => {
        TestBed.configureTestingModule({
          imports: [HttpClientTestingModule],
          providers: [
            {
              provide: AuthenticationService,
              useClass: MockAuthenticationService,
            },
          ],
        });
      }));
    
      it('should return true for a logged in user', () => {
        const guardResult = TestBed.runInInjectionContext(() = AuthorizedUserClaimGuard(createMockRoute(), createMockRouteState()));
        expect(guardResult).toBeTruthy();
      });
    }
    

    The guardResult type can differ depending on your implementation. You have a sync result and can compare it directly. Naren Murali from the previous answer expects the result to be a promise. In case it would be an observable I would recommend to compare the result with marbles.