Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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();
  });
}
like image 495
vijred Avatar asked May 17 '26 01:05

vijred


2 Answers

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.

like image 175
MoxxiManagarm Avatar answered May 18 '26 18:05

MoxxiManagarm


You have to use TestBed.runInInjectionContext to execute the function so that inject will work.

const result = await TestBed.runInInjectionContext(() => 
    AuthorizedUserClaimGuard(route, state));

Full Code:

import {
  TestBed,
} from '@angular/core/testing';
import {AuthorizedUserClaimGuard,
  AuthenticationService,
  ConfigurationService} from './guard';
import { HttpClientTestingModule } from '@angular/common/http/testing';
  
describe('AuthorizedUserClaimGuard', () => {
  let guard: any;
  let authentication: AuthenticationService;
  let configuration: ConfigurationService;

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

  beforeEach(() => {
    authentication = TestBed.inject(AuthenticationService);
    configuration = TestBed.inject(ConfigurationService);
    configuration.setConfiguration({});
  });

  it('should call logout when not authenticated and return false', async () => {
    spyOn(authentication, 'logout');
    const route: any = {};
    const state: any = {};
    const result = await TestBed.runInInjectionContext(() => AuthorizedUserClaimGuard(route, state));
    expect(authentication.logout).toHaveBeenCalled();
    expect(result).toBe(false);
  });

  it('should call true when permissions present', async () => {
    authentication.authenticated = true;
    configuration.authorizedClaims = [{}];
    const route: any = {};
    const state: any = {};
    const result = await TestBed.runInInjectionContext(() => AuthorizedUserClaimGuard(route, state));
    expect(result).toBe(true);
  });
});

Stackblitz Demo

like image 37
Naren Murali Avatar answered May 18 '26 20:05

Naren Murali



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!