Angular - Testing routing with back navigation

Viewed 1399

I am trying to test routing in an Angular application. I can successfully test forward navigation but I cannot test back navigation.

My test specification:

import {Location} from "@angular/common";
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
import {RouterTestingModule} from "@angular/router/testing";
import {Router} from "@angular/router";

import {
    HomeComponent,
    SearchComponent,
    AppComponent,
    routes
} from "./router"

describe('Router: App', () => {

  let location: Location;
  let router: Router;
  let fixture;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [    RouterTestingModule.withRoutes(routes)],
      declarations: [
        HomeComponent,
        SearchComponent,
        AppComponent
      ]
    });

    router = TestBed.get(Router);
    location = TestBed.get(Location);

    fixture = TestBed.createComponent(AppComponent);
  });

  it('location.back()', fakeAsync(() => {
    router.navigate(['']);
    tick();
    expect(router.routerState.snapshot.url).toBe('/home');

    router.navigate(['/search']);
    tick();
    expect(router.routerState.snapshot.url).toBe('/search');

    console.log('BACK')
    location.back()
    tick();
    expect(location.path()).toBe('/home');
    expect(router.routerState.snapshot.url).toBe('/home');
  })); 
});

When using router.navigate(), routerState is updated (I also checked that router.events sends navigation events). However, when calling location.back() the router state is not updated and I receive no router events through router.events.

What is the preferred way to test code, which uses back (and forward) navigation in Angular?

Thanks in advance.

1 Answers

I had the same problem and also had no clue. So I started digging in how RouterTestingModule sets up the 2 most important 'components' in your use case: Router and Location.

The used Router is real but it is handed SpyLocation. So I thought that it just fakes popstate and doesn't modify router state in any way, but it actually has the same side effect as the real Location does - it emits an event (popstate in your case). Now Router, regardless of the Location implementation, subscribes to those events by calling its setUpLocationChangeListener, and when an event occurs like popstate, it reflect upon it in its routerState. And I found, that in my test case, and apparently in yours as well, the event subscription was not created because this method was not called.

The solution is simple. You must call router.initialNavigation() before you test any of your popstate code. Ideally you do it as the first operation on Router. Because, as you can see below (and this is the only place where this is done) the location change listener is set up there.

from router.ts

initialNavigation(): void {
    this.setUpLocationChangeListener();                                                                                                                                                                                                                                            
    if (this.navigationId === 0) {
        this.navigateByUrl(this.location.path(true), {replaceUrl: true})      
    }
}
Related