-1
import {Router, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree} from '@angular/router';

@Injectable({provideIn: 'root'})
export class FooGuard implements CanActivate {
  constructor (private readonly router: Router) {}

  canActivate (next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<UrlTree> {
    const xxx = myMagic(next); // irrelevant app logic that depends on next url only

    return (async () => this.router.parseUrl(xxx));
  }
}

Trying to find an example of testing code for this piece without a page of extra boilerplate. Hopefully can get something closer to 5-6 lines of code per mock. Need:

  • Mock for Router
  • Mock for ActivatedSnapshot
c69
  • 19,951
  • 7
  • 52
  • 82
  • i just want to test the `canActivate` method of the guard, and i DONT want to write mock implementing/hardcoding all the dozens of properties of real `ActivatedRouteSnapshot` – c69 Jan 22 '20 at 04:09
  • You don't need to mock all the properties. Just mock the property by **spyOn()** which is you have used in can activate(), Please visit https://stackoverflow.com/questions/46831630/how-to-mock-route-snapshot-params/52895293 – Ramesh Rajendran Jan 22 '20 at 04:11

1 Answers1

5

Take a look at RouterTestingModule. It's not a six lines of code solution, but a pretty compact one. I think it's the best way to test guards and routes:

import { Component, Injectable } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from "@angular/router";
import { RouterTestingModule } from '@angular/router/testing';

@Injectable({
    providedIn: 'root'
})
export class FooGuard implements CanActivate {
    constructor (private readonly router: Router) {}

    canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): UrlTree {
      const url = "some/new/url"; // create an url based on next snapshot
      return this.router.parseUrl(url);
    }
  }

@Component({ template: '' })
export class DummyComponent {}

function setup(): {
    router: Router
} {
    TestBed.configureTestingModule({
        imports: [
            RouterTestingModule.withRoutes([
                { path: 'test', component: DummyComponent, canActivate: [ FooGuard ] },
                { path: 'some/new/url', component: DummyComponent }
            ])
        ],
        declarations: [
            DummyComponent
        ]
    });

    return {
        router: TestBed.get(Router)
    };
}

describe(FooGuard.name, () => {
    it('should redirect to a new url', async () => {
        const { router } = setup();
        await router.navigateByUrl('/test');
        expect(router.url).toBe('/some/new/url');
    })
});


Actually the regular Router.forRoot() should also work in this case, but RouterTestingModule must be more suitable for testing. For example the last one provides custom Location implementation.

Valeriy Katkov
  • 33,616
  • 20
  • 100
  • 123