Before we go I need to clarify some confusion existing in Angular world with Location object. There’s Location from @angular/common and the native DOM Location which is available by default. In spite of, Angular's version provides .go() function, in fact, it only interacts with router and doesn't reload the page as DOM object do. So, for real browser interaction you have to use the DOM version, which brings you to a problem how to test this?

Unfortunately, it’s not possible to spy on it because it’s not a writable object.

const spy = spyOn(location, ‘assign’).and.stub();

... Error: <spyOn> : assign is not declared writable or has no setter

Here’s the typical Jasmine error, if you try to do it this way.

Dependency Injection for the rescue

Instead of manipulation location directly we need to inject it our component with Dependency injection mechanism. Having it injected, we can replace it with test double later.

A brief introduction of how DI works. If you’re familiar with it, just skip to the code sample. Here’s the typical providers section in some module

providers: [

MyService, // short way

{ provide: MyService, useClass: MyService } // full way

]

Every declaration of providers in your modules is actually equivalent to full provider description, where MyService acts not as class name, but as a token to identify your service. This token Angular takes from TypeScript metadata about your classes. See Dependency Injection guide for details.

Hopefully, Angular provides us InjectionToken class which we can use to generate any custom token.



...

export const LOCATION_TOKEN = new InjectionToken<Location>('Window location object'); import { InjectionToken } from '@angular/core';...export const LOCATION_TOKEN = new InjectionToken ('Window location object');

To be able to use this token we have to use special property decorator @Inject also available in Angular.

@Inject(LOCATION_TOKEN) private location: Location

Now, let’s combine all pieces together

export const LOCATION_TOKEN = new InjectionToken<Location>('Window location object'); @Component({

providers: [

{ provide: LOCATION_TOKEN, useValue: window.location }

]

})

export class SomeComponent {

constructor(@Inject(LOCATION_TOKEN) private location: Location) {} useIt() {

this.location.assign('xxx');

}

}

This technique can be used not only to inject Location object but any other entities that don’t have particular classes for it, like other WebAPI objects, application configuration etc.