70

I am trying to test my component with angular 2 final, but I get an error because the component uses the routerLink directive. I get the following error:

Can't bind to 'routerLink' since it isn't a known property of 'a'.

This is the relevant code of the ListComponent template

<a 
  *ngFor="let item of data.list" 
  class="box"
  routerLink="/settings/{{collectionName}}/edit/{{item._id}}">

And here is my test.

import { TestBed } from '@angular/core/testing';

import { ListComponent } from './list.component';
import { defaultData, collectionName } from '../../config';
import { initialState } from '../../reducers/reducer';


const data = {
  sort: initialState.sort,
  list: [defaultData, defaultData],
};

describe(`${collectionName} ListComponent`, () => {
  let fixture;
  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [
        ListComponent,
      ],
    }).compileComponents(); // compile template and css;
    fixture = TestBed.createComponent(ListComponent);
    fixture.componentInstance.data = data;
    fixture.detectChanges();
  });

  it('should render 2 items in list', () => {
    const el = fixture.debugElement.nativeElement;
    expect(el.querySelectorAll('.box').length).toBe(3);
  });
});

I looked at several answers to similar questions but could not find a solution that worked for me.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
select
  • 2,513
  • 2
  • 25
  • 36

3 Answers3

130

You need to configure all the routing. For testing, rather than using the RouterModule, you can use the RouterTestingModule from @angular/router/testing, where you can set up some mock routes. You will also need to import the CommonModule from @angular/common for your *ngFor. Below is a complete passing test

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { By } from '@angular/platform-browser';
import { Location, CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { TestBed, inject, async } from '@angular/core/testing';

@Component({
  template: `
    <a routerLink="/settings/{{collName}}/edit/{{item._id}}">link</a>
    <router-outlet></router-outlet>
  `
})
class TestComponent {
  collName = 'testing';
  item = {
    _id: 1
  };
}

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

describe('component: TestComponent', function () {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        CommonModule,
        RouterTestingModule.withRoutes([
         { path: 'settings/:collection/edit/:item', component: DummyComponent }
        ])
      ],
      declarations: [ TestComponent, DummyComponent ]
    });
  });

  it('should go to url',
    async(inject([Router, Location], (router: Router, location: Location) => {

    let fixture = TestBed.createComponent(TestComponent);
    fixture.detectChanges();

    fixture.debugElement.query(By.css('a')).nativeElement.click();
    fixture.whenStable().then(() => {
      expect(location.path()).toEqual('/settings/testing/edit/1');
      console.log('after expect');
    });
  })));
});

UPDATE

Another option, if you just want to test that the routes are rendered correctly, without trying to navigate...

You an just import the RouterTestingModule without configuring any routes

imports: [ RouterTestingModule ]

then just check that the link is rendered with the correct URL path, e.g.

let href = fixture.debugElement.query(By.css('a')).nativeElement
    .getAttribute('href');
expect(href).toEqual('/settings/testing/edit/1');
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Thanks, this helps already alot, but I cannot get the unit test to fail. It seems that the because of `async` everything in this block is ginored. I tried to add a done function to the `it('...', (done) => {async(... done())}` but then I get `Error: Timeout` from the test. – select Sep 19 '16 at 19:04
  • My bad, I had the `async` wrapped in another function and not as second parameter of the `it`. It works now, thanks. – select Sep 19 '16 at 19:12
  • This is fantastic. Finally found an answer that applies to 2.0 Final (I'm using 2.1)! Since I was going to need the same configuration in a lot of tests, I created a test-only module that configured RouterTestingModule and declared the DummyComponent. Then I put that module and the DummyComponent into a test/ subfolder, which I can then import into any of my unit tests. Thanks @peeskillet – Mac Oct 27 '16 at 21:29
  • 1
    Actually, if you only need to render routerLink you don't have to setup the routing – s-f Feb 06 '17 at 12:26
  • Very thorough and solid answer, thank you. Resolves all routerLink testing issues I saw. – aphexddb Apr 10 '17 at 18:12
  • 1
    `getAttribute('href')` returns `null` for me, while it works in app. Is there something I miss? – jlang Nov 28 '19 at 11:43
  • @Paul Samsotha do you know how to do the same with providers array? I could not get this working – Juliyanage Silva Jan 14 '20 at 15:26
23

If you are not testing router related stuff, you can configure the test to ignore unknown directives with 'NO_ERRORS_SCHEMA'

 import { NO_ERRORS_SCHEMA } from '@angular/core';
 TestBed.configureTestingModule({
   declarations: [
     ListComponent,
   ],
   schemas: [ NO_ERRORS_SCHEMA ]
 });
mahulst
  • 443
  • 2
  • 10
  • Thanks that is even a more specific solution to my problem, thoug I learned also form the first one. Could you please also share where to import `NO_ERRORS_SCHEMA` from. – select Sep 22 '16 at 08:34
  • `NO_ERRORS_SCHEMA` is currently [flagged](https://angular.io/docs/ts/latest/api/core/index/NO_ERRORS_SCHEMA-let.html) as `Experimental` – CalvinDale Dec 17 '16 at 17:12
  • 2
    @CalvinDale That shouldn't matter. A lot of things are marked experimental (including commonly used classes like Http). https://angular.io/docs/ts/latest/api/#!?status=experimental – Kevin Babcock Mar 10 '17 at 20:51
  • 1
    You should pretty much never use `NO_ERRORS_SCHEMA` it does a lot more than just fix this. It also causes ALL errors to pass in tests – Liam Feb 07 '22 at 11:28
12

To write a test case for routerLink. You can follow the below steps.

  1. Import RouterTestingModule and RouterLinkWithHref.

    import { RouterTestingModule } from '@angular/router/testing';
    import { RouterLinkWithHref } from '@angular/router';
    
  2. Import RouterTestingModule in your module

    TestBed.configureTestingModule({
      imports: [ RouterTestingModule.withRoutes([])],
      declarations: [ TestingComponent ]
    })
    
  3. In test case find the directive RouterLinkWithHref tot test for the existence of the link.

    it('should have a link to /', () => {
      const debugElements = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref));
      const index = debugElements.findIndex(de => {
        return de.properties['href'] === '/';
      });
      expect(index).toBeGreaterThan(-1);
    });
    
Massimiliano Kraus
  • 3,638
  • 5
  • 27
  • 47
Anurag Singh Bisht
  • 2,683
  • 4
  • 20
  • 26
  • 2
    The import work also as `imports: [ RouterTestingModule ]` – Spiral Out Apr 14 '18 at 15:51
  • `linkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); routerLinks = linkDes.map(de=>de.injector.get(RouterLinkWithHref)); expect(routerLinks[0].href).toBe('/settings/testing/edit/1');` – Alex Buchatski Jun 20 '21 at 12:07