RouterTestingModule not providing provider for Location

Issue

I am unit-testing a Component which uses two other components. The other components are created when button is clicked

.html

    <div id="homepage-top-div" class="homepage-component-css-grid-container">  ...
        <button id="get-question-list-button" [routerLink]="questionListRouterLink" class="btn content-div__button--blue css-grid-item-button-div btn-sm">See Questions</button>
      </div>
      <div id="practice-component-top-div" class="css-grid-container-div common-styles-div--white"> <!-- 4 rows, 1 column-->
...
        <button id="new-question-button" [routerLink]="newQuestionRouterLink"  class="btn content-div__button--blue css-grid-item-button-div btn-sm">Create a Question</button>
      </div>
    </div>

.ts

export class HomepageContentComponentComponent implements OnInit {

  public questionListRouterLink="/practice-question-list";
  public newQuestionRouterLink="/new-practice-question";


  constructor() {

  }

  ngOnInit() {
  }

}

I created a spec but I get the following error when I run it – Error: StaticInjectorError[Location]:
NullInjectorError: No provider for Location!

The spec is

import {async, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing';
import { HomepageContentComponentComponent } from './homepage-content-component.component';
import {RouterTestingModule} from "@angular/router/testing";
import {AppRoutingModule} from "../app-routing.module";
import {Router} from "@angular/router";
import {routes} from '../app-routing.module';
import {NewPracticeQuestionComponent} from "../new-practice-question/new-practice-question.component";
import {PraticeQuestionListComponent} from "../pratice-question-list/pratice-question-list.component";
import {NgModule} from "@angular/core";
import {AppModule} from "../app.module";

fdescribe('HomepageContentComponentComponent', () => {
  let component: HomepageContentComponentComponent;
  let fixture: ComponentFixture<HomepageContentComponentComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports:[
        AppModule,
        AppRoutingModule,
        RouterTestingModule.withRoutes(routes) //<-- I SUPPOSE THIS STEP SHOULD PROVIDE ROUTERTESTINGMODULE WITH PROVIDERS FOR LOCATION BUT IT SEEMS IT ISN'T
      ],
      declarations: [ HomepageContentComponentComponent,
                  NewPracticeQuestionComponent,
        PraticeQuestionListComponent]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HomepageContentComponentComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should navigate to New Questions Component when New Question button is clicked',fakeAsync(()=>{
    let router:Router;
    let location:Location;
    router = TestBed.get(Router);
    location = TestBed.get(Location);
    console.log('initial router is ',router);
    console.log('initial location is ',location);
    router.initialNavigation();
    router.navigate(['new-practice-question']);
    tick();
    console.log('new router is ',router);
    console.log('new location is ',location);

    expect(location.pathname).toBe('/new-practice-question');
  }));
});

Solution

The reason why the wrong Location was being picked and why import {Location} from "@angular/common"; helped is that without importing it, TypeScript actually thinks you want to use the type of window.location, which is completely different type than the one you are looking for.

As the type is a token to retrieve desired instance from angular’s DI registry, you don’t get anything if you use a wrong type. When you comment out the import and ‘click through’ the Location type in your IDE, you will see that the picked type is actually a DOM interface, not the Angular implementation. This DOM interface is implicitly there once you have "lib": ["dom"] in your tsconfig.json.

Importing Location from angular explicitly overshadows Location from DOM, the right type is picked and the right Location instance is found in angular’s DI registry.

It is actually pretty easy mistake to make (I made it myself), because even if you forget to import the Location, the DOM Location is picked and you get no feedback in your IDE at all and you are left wondering, why the DI fails.

Answered By – Skocdopole

Answer Checked By – Candace Johnson (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.