angularangular-routingangular-testangular-guards

Testing a functional Angular Routing guard


Hello since the class guards are deprecated, I moved mine to a functional, but my test broke and I'm really confused of the injectable service of keycloak and the parameters of the guard.

Here is the Guard:

export const authGuard: CanActivateFn = async (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
): Promise<boolean> => {
  const keycloak = inject(KeycloakService);
  const isLogged = await keycloak.isLoggedIn();

  if (!isLogged) {
    await keycloak.login({
      redirectUri: window.location.origin + state.url,
    });
    return false;
  }
  return true;
};

This is my test:

import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { KeycloakService } from 'keycloak-angular';
import { authGuard } from './auth.guard';

describe('authGuard', () => {
  let router: Router;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule],
      // Provide the MockKeycloakService for the KeycloakService dependency
      providers: [{ provide: KeycloakService, useClass: MockKeycloakService }],
    });

    // Get the Router instance
    router = TestBed.inject(Router);
  });

  // Step 1: Mock KeycloakService
  class MockKeycloakService {
    isLoggedIn(): Promise<boolean> {
      // Simulate that the user is logged in for testing purposes
      return Promise.resolve(true);
    }

    login(options: any): Promise<void> {
      // Simulate the login process for testing purposes
      return Promise.resolve();
    }
  }

  it('should allow access when the user is logged in', async () => {
    // Arrange
    const route: ActivatedRouteSnapshot = {} as any;
    const state: RouterStateSnapshot = {} as any;

    // Act
    const result = await authGuard(route, state);

    // Assert
    expect(result).toBe(true);
  });

  it('should redirect to login when the user is not logged in', async () => {
    // Arrange
    const route: ActivatedRouteSnapshot = {} as any;
    const state: RouterStateSnapshot = {
      url: '/consent-initiation',
    } as any;

    // Create a new instance of the MockKeycloakService to simulate the user not logged in
    const keycloakService: MockKeycloakService = TestBed.inject(KeycloakService) as any;
    spyOn(keycloakService, 'isLoggedIn').and.returnValue(Promise.resolve(false));

    // Act
    const result = await authGuard(route, state);

    // Assert
    expect(result).toBe(false);
    // You can also test whether the KeycloakService.login() method was called with the correct redirectUri.
  });
});

this is the error I get -> Error: NG0203: inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with runInInjectionContext. Find more at https://angular.io/errors/NG0203

Can't figure out how to fix it. any suggestions ?


Solution

  • You need to use TestBed.runInInjectionContext() when running your auth guard:

    // incorrect
    const result = await authGuard(route, state);
    
    // correct
    const result = await TestBed.runInInjectionContext(() => authGuard(route, state));