6
votes

Error Description

Angular version: 2.3.1

My unit test fails to create the component - I know this issue is related to the [routerLink] and [routerLinkActive] directives because removing them from the template allows the test to create the component.

TEMPLATE

<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
  <button class="navbar-toggle" data-toggle="collapse" data-target="#iotahoe-top-navigation">
    <span class="sr-only">Toggle navigation</span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
  </button>
  <a class="navbar-brand" [routerLink]="['/']">IoTahoe</a>
</div>
<div class="collapse navbar-collapse" id="iotahoe-top-navigation">
  <ul *ngIf="isAuthenticated()" class="nav navbar-nav navbar-right">
    <li [routerLinkActive]="['active']"><a [routerLink]="['/dashboard']">Dashboard</a></li>
    <li [routerLinkActive]="['active']"><a [routerLink]="['/browse']">Browse</a></li>
    <li [routerLinkActive]="['active']"><a [routerLink]="['/admin']">Admin</a></li>
    <li [routerLinkActive]="['active']"><a (click)="onLogout()" style="cursor: pointer;">Logout</a></li>
  </ul>
</div>

TYPESCRIPT

import { Component, OnInit } from '@angular/core';
import { AuthenticationService } from   '../../authentication/authentication.service';
import { Router } from '@angular/router';

@Component({
moduleId: module.id.toString(),
selector: 'app-top-navbar',
templateUrl: './top-navbar.component.html',
styleUrls: ['./top-navbar.component.css']
})
export class TopNavbarComponent implements OnInit {

constructor(private authenticationService: AuthenticationService, private router: Router) { }

ngOnInit() {
}

isAuthenticated() {
    return this.authenticationService.isLoggedIn;
}

onLogout() {
    this.authenticationService.logout().subscribe(() => {
        return this.router.navigate(['/login']);
    });
}

}

TEST SPEC

/* tslint:disable:no-unused-variable */
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import {DebugElement, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, Component} from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { Location, CommonModule } from '@angular/common';
import { TopNavbarComponent } from './top-navbar.component';
import { AuthenticationService } from '../../authentication/authentication.service';
import { Router } from '@angular/router';
import {ReactiveFormsModule} from "@angular/forms";

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


describe('TopNavbarComponent', () => {
let component: TopNavbarComponent;
let fixture: ComponentFixture<TopNavbarComponent>;
let authenticationService: AuthenticationService;

beforeEach(async(() => {
    const authenticationServiceStub = {
        isLoggedIn: false
    };

    const routerStub = {
        navigate: jasmine.createSpy('navigate'),
        navigateByUrl: jasmine.createSpy('navigateByUrl')
    };

    TestBed.configureTestingModule({
        declarations: [ TopNavbarComponent, DummyComponent ],
        imports:[CommonModule, ReactiveFormsModule,     RouterTestingModule.withRoutes(
            [
                { path: '/', component:DummyComponent },
                { path: '/login', component:DummyComponent },
                { path: '/dashboard', component:DummyComponent },
                { path: '/browse', component:DummyComponent },
                { path: '/admin', component:DummyComponent }
            ])],
        providers: [
            { provide: AuthenticationService, useValue: authenticationServiceStub },
            { provide: Router, useValue: routerStub }
            ]
    }).compileComponents();
}));

beforeEach(() => {
    fixture = TestBed.createComponent(TopNavbarComponent);
    component = fixture.componentInstance;
    authenticationService = TestBed.get(AuthenticationService);
    fixture.detectChanges();
});

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

ERROR

zone.js:155 Uncaught Error: Error in package:407:9:6 caused by: Cannot read property 'root' of undefined at ViewWrappedError.Error (native) at ViewWrappedError.ZoneAwareError (localhost:9876/base/src/test.ts:133296:33) at ViewWrappedError.BaseError [as constructor] (localhost:9876/base/src/test.ts:35630:16) at ViewWrappedError.WrappedError [as constructor] (localhost:9876/base/src/test.ts:35695:16) at new ViewWrappedError (localhost:9876/base/src/test.ts:68018:16) at DebugAppView._rethrowWithContext (localhost:9876/base/src/test.ts:108242:23) at DebugAppView.create (localhost:9876/base/src/test.ts:108142:18) at DebugAppView.View_TopNavbarComponent_Host0.createInternal (/DynamicTestModule/TopNavbarComponent/host.ngfactory.js:16:19) at DebugAppView.AppView.createHostView (localhost:9876/base/src/test.ts:107700:21) at DebugAppView.createHostView (localhost:9876/base/src/test.ts:108156:52) at ComponentFactory.create (localhost:9876/base/src/test.ts:49830:25) at initComponent (localhost:9876/base/src/test.ts:6425:53) at ZoneDelegate.invoke (localhost:9876/base/src/test.ts:132727:26) at ProxyZoneSpec.onInvoke (localhost:9876/base/src/test.ts:95802:39) at ZoneDelegate.invoke (localhost:9876/base/src/test.ts:132726:32)Zone.runTask @ zone.js:155ZoneTask.invoke @ zone.js:345data.args.(anonymous function) @ zone.js:1376

2

2 Answers

12
votes

The routerLink directives need a real router, but you are mocking it. A couple things I can see you doing:

  • If you don't plan to click the links during testing, then you can mock those directive to just make "noop" directives so the compiler doesn't complain. e.g.

    @Directive({
        selector: '[routerLink], [routerLinkActive]'
    })
    class DummyRouterLinkDirective {}
    

    Then just add that to the declarations of the test module. With this you don't really need to configure the RouterTestingModule at all. You could probably get rid of that.

  • Also if you don't plan to click test, another option (without needing to create dummy directives is to just ignore the errors of missing directives:

    schemas: [ NO_ERRORS_SCHEMA ]
    

    You would add this to the test module configuration (as seen here). This might not be desirable in some cases, as it could also ignore errors that you actually want detected, which could lead to hard to debug tests.

  • If you would actually like to click the links and test routing, then you can use the real router. You can see an example of how you can test the navigation, using the Location, as seen in this post.

0
votes

For me the solution with the non-mocked routing worked. But I found out that I also needed to add a

<router-outlet></router-outlet>

to the component using "routerlink active".