Testing Your Functional Route Guards (Angular)


Angular recently got a new feature called functional route guards.

Functional guards were a bold move away from the Typescript class-style route guards to just a simple function.

And I think it was a great idea!

good-idea

But one of the down-sides of this new change was that the functional route guards do not have a constructor to inject Angular dependencies.

Which raised a bit of a ruckus in this GitHub issue of folks complaining about the constructor being gone.

Instead, we use the inject function

inject

Since the constructor no longer exists we have to instead use the inject function to inject our dependencies.

Something like this.

import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';

export const appGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
  const router = inject(Router);
  const token = localStorage.getItem("token");
  if (token) {
    return true;
  }
  return router.parseUrl("/auth/login");
};

Which works like a clown on steroids.

In other words, the inject function does the job well for injecting dependencies into the function based route guards.

So how do we test the functional route guard?

As you can see in the example above, if there is a token saved to local storage we return true. Otherwise, we return false.

So in this case we expect our route guard to return a string value /auth/login.

Here's the full test example - a simple code example of how to test the functional route guard.

import { TestBed } from '@angular/core/testing';
import { ActivatedRouteSnapshot, CanActivateFn, RouterModule, RouterStateSnapshot, UrlTree } from '@angular/router';

import { appGuard } from './app.guard';

describe('appGuard', () => {
  const executeGuard: CanActivateFn = (...guardParameters) => 
      TestBed.runInInjectionContext(() => appGuard(...guardParameters));

  beforeEach(() => {
    TestBed.configureTestingModule({imports: [RouterModule.forRoot([])]});
  });

  it('should return login page', () => {
    const route: ActivatedRouteSnapshot = {} as any;
    const state: RouterStateSnapshot = { url: '/dashboard' } as any;

    const result = executeGuard(route, state) as UrlTree;

    expect(result.toString()).toEqual("/auth/login")
  });
});

And that passes with green colors!

Conclusion

And that, my friend, is how to test the new functional route guards in Angular.

Please drop me a note if you have any questions.

Till next time,

signature

Daniel Kreider - Angular Developer