Angular 2 unit testing components with routerLink
Asked Answered
L

4

71

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.

Leucoderma answered 19/9, 2016 at 16:33 Comment(0)
F
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');
Fourierism answered 19/9, 2016 at 17:37 Comment(7)
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.Leucoderma
My bad, I had the async wrapped in another function and not as second parameter of the it. It works now, thanks.Leucoderma
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 @peeskilletMcguire
Actually, if you only need to render routerLink you don't have to setup the routingMazurek
Very thorough and solid answer, thank you. Resolves all routerLink testing issues I saw.Kunin
getAttribute('href') returns null for me, while it works in app. Is there something I miss?Selfrespect
@Paul Samsotha do you know how to do the same with providers array? I could not get this workingLeoine
C
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 ]
 });
Christoper answered 20/9, 2016 at 6:45 Comment(4)
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.Leucoderma
NO_ERRORS_SCHEMA is currently flagged as ExperimentalBoyce
@Boyce That shouldn't matter. A lot of things are marked experimental (including commonly used classes like Http). angular.io/docs/ts/latest/api/#!?status=experimentalBulwerlytton
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 testsHighkeyed
D
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);
    });
    
Demolish answered 3/8, 2017 at 5:42 Comment(2)
The import work also as imports: [ RouterTestingModule ]Ioved
linkDes = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); routerLinks = linkDes.map(de=>de.injector.get(RouterLinkWithHref)); expect(routerLinks[0].href).toBe('/settings/testing/edit/1');Hardaway
E
0

How to check that the Router actually triggered the URL change


Solution inspired by https://medium.com/@douglascaina/angular-tdd-how-to-test-routerlink-or-url-change-494f18208443


My setup (using ng-mocks):

class MockComponent {}

// ...

MockBuilder(MyComponent)
  .keep(RouterTestingModule.withRoutes([{ path: 'my/path', component: MockComponent }]))
  .keep(RouterLink)

My test case:

it('when clicked, myLinkElement should lead to /my/path', fakeAsync(() => {
  const { page, router } = setup();
  page.detectChanges();

  page.click(page.myLinkElement().nativeElement);
  tick();

  expect(router.url).toEqual('/my/path');
}));
Eberhardt answered 29/11, 2023 at 12:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.