6

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 Answers2

12

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.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Hi, thanks for response, I added the mock directive and included it in the declarations for the test module but the same issue still causes the test to fail. –  Feb 04 '17 at 11:52
  • 3
    Did you remove the RouterTestingModule? – Paul Samsotha Feb 04 '17 at 11:54
  • 2
    No I hadn't but when I did it worked! Awesome, thanks for the assistance. –  Feb 04 '17 at 11:56
  • Come to think of it, I think just importing the `RouterTestingModule` (without the call to `withRoutes`) should work also. I may be wrong that the real router is needed to just compile. I think maybe calling the `withRoutes` requires the real router. You can try to just import the router testing module, and that way you don't need dummy directives. I think this would be the preferred way – Paul Samsotha Feb 04 '17 at 12:01
  • In my case, my component was importing `ActivatedRoute` as well, and instead of raising an error like `Missing provider for ActivatedRoute`, this root error was raised. – luiscvalmeida Jul 28 '17 at 21:38
0

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".

seawave_23
  • 1,169
  • 2
  • 12
  • 23