Angular 2 Jasmine Can't bind to 'routerLink' since it isn't a known property of 'a'

20,969

Solution 1

The Angular Testing docs address this by using RouterLinkDirectiveStub and RouterOutletStubComponent so that routerLink is a known property of <a>.

Basically it says that using RouterOutletStubComponent is a safe way to test routerLinks without all the complications and errors of using the real RouterOutlet. Your project needs to know it exists so it doesn't throw errors but it doesn't need to actually do anything in this case.

The RouterLinkDirectiveStub enables you to click on <a> links with routerLink directive and get just enough information to test that it is being clicked (navigatedTo) and going to the correct route (linkParams). Any more functionality than that and you really aren't testing your component in isolation any more.

Take a look at their Tests Demo in app/app.component.spec.ts. Grab the testing/router-link-directive-stub.ts and add to your project. Then you will inject the 2 stubbed items into your TestBed declarations.

Solution 2

Just import RouterTestingModule in TestBed.configureTestingModule of your components spec.ts file

Eg:

import { RouterTestingModule } from '@angular/router/testing';


TestBed.configureTestingModule({
  imports: [RouterTestingModule],
  declarations: [ ComponentHeaderComponent ]
})
Share:
20,969
Bhetzie
Author by

Bhetzie

Updated on July 15, 2022

Comments

  • Bhetzie
    Bhetzie almost 2 years

    I'm creating a unit test for my Navbar Component and I'm getting an error:

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

    Navbar Component TS

    import { Component } from '@angular/core';
    import { Router } from '@angular/router';
    import { NavActiveService } from '../../../services/navactive.service';
    import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
    
    @Component({
      moduleId: module.id,
      selector: 'my-navbar',
      templateUrl: 'navbar.component.html',
      styleUrls:['navbar.component.css'],
      providers: [NavActiveService]
    })
    export class NavComponent {
      showNavBar: boolean = true;
    
      constructor(private router: Router,
                  private navactiveservice:NavActiveService,
                  private globalEventsManager: GlobalEventsManager){
    
        this.globalEventsManager.showNavBar.subscribe((mode:boolean)=>{
          this.showNavBar = mode;
        });
    
      }
    
    }
    

    Navbar Component Spec

    import { ComponentFixture, TestBed, async } from '@angular/core/testing';    
    import { NavComponent } from './navbar.component';
    import { DebugElement }    from '@angular/core';
    import { By }              from '@angular/platform-browser';
    import { Router } from '@angular/router';
    
    export function main() {
        describe('Navbar component', () => {
    
            let de: DebugElement;
            let comp: NavComponent;
            let fixture: ComponentFixture<NavComponent>;
            let router: Router;
    
            // preparing module for testing
            beforeEach(async(() => {
                TestBed.configureTestingModule({
                    declarations: [NavComponent],
                }).compileComponents().then(() => {
    
                    fixture = TestBed.createComponent(NavComponent);
                    comp = fixture.componentInstance;
                    de = fixture.debugElement.query(By.css('p'));
    
                });
            }));
    
    
            it('should create component', () => expect(comp).toBeDefined());
    
    
    /*        it('should have expected <p> text', () => {
                fixture.detectChanges();
                const h1 = de.nativeElement;
                expect(h1.innerText).toMatch(" ");
            });*/
    
    
        });
    }
    

    I realize that I need to add router as a spy, but if I add it as a SpyObj and declare it as a provider I get the same error.

    Is there a better way for me to add fix this error?

    EDIT: Working Unit Test

    Built this unit test based on the answer:

    import { ComponentFixture, TestBed, async  } from '@angular/core/testing';
    import { NavComponent } from './navbar.component';
    import { DebugElement }    from '@angular/core';
    import { By }              from '@angular/platform-browser';
    import { RouterLinkStubDirective, RouterOutletStubComponent } from '../../../../test/router-stubs';
    import { Router } from '@angular/router';
    import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
    import { RouterModule } from '@angular/router';
    import { SharedModule } from '../shared.module';
    
    
    export function main() {
        let comp: NavComponent;
        let fixture: ComponentFixture<NavComponent>;
        let mockRouter:any;
        class MockRouter {
            //noinspection TypeScriptUnresolvedFunction
            navigate = jasmine.createSpy('navigate');
        }
    
        describe('Navbar Componenet', () => {
    
            beforeEach( async(() => {
                mockRouter = new MockRouter();
                TestBed.configureTestingModule({
                    imports: [ SharedModule ]
                })
    
                // Get rid of app's Router configuration otherwise many failures.
                // Doing so removes Router declarations; add the Router stubs
                    .overrideModule(SharedModule, {
                        remove: {
                            imports: [ RouterModule ],
    
                        },
                        add: {
                            declarations: [ RouterLinkStubDirective, RouterOutletStubComponent ],
                            providers: [ { provide: Router, useValue: mockRouter }, GlobalEventsManager ],
                        }
                    })
    
                    .compileComponents()
    
                    .then(() => {
                        fixture = TestBed.createComponent(NavComponent);
                        comp    = fixture.componentInstance;
                    });
            }));
    
            tests();
        });
    
    
            function tests() {
                let links: RouterLinkStubDirective[];
                let linkDes: DebugElement[];
    
                beforeEach(() => {
                    // trigger initial data binding
                    fixture.detectChanges();
    
                    // find DebugElements with an attached RouterLinkStubDirective
                    linkDes = fixture.debugElement
                        .queryAll(By.directive(RouterLinkStubDirective));
    
                    // get the attached link directive instances using the DebugElement injectors
                    links = linkDes
                        .map(de => de.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
                });
    
                it('can instantiate it', () => {
                    expect(comp).not.toBeNull();
                });
    
                it('can get RouterLinks from template', () => {
                    expect(links.length).toBe(5, 'should have 5 links');
                    expect(links[0].linkParams).toBe( '/', '1st link should go to Home');
                    expect(links[1].linkParams).toBe('/', '2nd link should go to Home');
    expect(links[2].linkParams).toBe('/upload', '3rd link should go to Upload');
                    expect(links[3].linkParams).toBe('/about', '4th link should to to About');
                    expect(links[4].linkParams).toBe('/login', '5th link should go to Logout');
                });
    
                it('can click Home link in template', () => {
                    const uploadLinkDe = linkDes[1];
                    const uploadLink = links[1];
    
                    expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
    
                    uploadLinkDe.triggerEventHandler('click', null);
                    fixture.detectChanges();
    
                    expect(uploadLink.navigatedTo).toBe('/');
                });
    
    
                it('can click upload link in template', () => {
                    const uploadLinkDe = linkDes[2];
                    const uploadLink = links[2];
    
                    expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
    
                    uploadLinkDe.triggerEventHandler('click', null);
                    fixture.detectChanges();
    
                    expect(uploadLink.navigatedTo).toBe('/upload');
                });
    
                it('can click about link in template', () => {
                    const uploadLinkDe = linkDes[3];
                    const uploadLink = links[3];
    
                    expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
    
                    uploadLinkDe.triggerEventHandler('click', null);
                    fixture.detectChanges();
    
                    expect(uploadLink.navigatedTo).toBe('/about');
                });
    
                it('can click logout link in template', () => {
                    const uploadLinkDe = linkDes[4];
                    const uploadLink = links[4];
    
                    expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
    
                    uploadLinkDe.triggerEventHandler('click', null);
                    fixture.detectChanges();
    
                    expect(uploadLink.navigatedTo).toBe('/login');
                });
            }
    }
    
  • Bhetzie
    Bhetzie over 7 years
    Added my working unit test, hopefully can help someone else if they're struggling. Thanks again!
  • FlavorScape
    FlavorScape over 7 years
    i don't understand why these stubs don't ship in /master
  • ForrestLyman
    ForrestLyman about 7 years
    I ran into an issue with these stubs, ts-lint complains that the selector should be prefixed with app: The selector of the directive "RouterLinkStubDirective" should have prefix "app"
  • garryp
    garryp about 7 years
    404 on the demo link :-(
  • Bhetzie
    Bhetzie almost 7 years
    I believe you can get the router stubs from this link now; they changed the documentation: angular.io/generated/live-examples/testing/bag-specs.eplnkr
  • NitinSingh
    NitinSingh over 6 years
    The link will work when you go to the Docs page and then click the link... Somehow its not linkable directly
  • Deeksha Bilochi
    Deeksha Bilochi over 5 years
    demo link not found
  • jellybeans
    jellybeans almost 4 years
    This is my preferred way for unit tests as it allows you to spy on the routes without actually changing the route. angular.io/api/router/testing/RouterTestingModule#descriptio‌​n
  • Tanzeel
    Tanzeel over 3 years
    no offense but i think this should be the accepted answer