angular 2 component with ngrx - errors when run unit-testing - unit-testing

I'm working on angular 2 project, using ngrx (for Redux logic) and rxjs (for observable).
Now I try to testing the project by running the auto-created .spec.ts files.
Some of the testings are fail, all of them are testing of components which use Store (from ngrx).
Here is one of them:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { MyComponent } from './overlay-menu-item.component';
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent ],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I look after the problem, the error was:
No provider to Store
So I add: (I use jasmine for testing)
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent ],
providers: [Store]
})
.compileComponents();
}));
Now I got other error:
No provider for StateObservable
So I add:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MyComponent ],
providers: [Store, StateObservable]
})
.compileComponents();
}));
But now I got new error that I don't know what do to with it:
Failed: Can't resolve all parameters for StateObservable: (?).
Error: Can't resolve all parameters for StateObservable: (?).
at syntaxError [mywebprojectPath]/node_modules/#angular/compiler/#angular/compiler.es5.js:1689:22)
What should I do?

You should create a MockStore.
In short:
import { Action } from '#ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { map } from 'rxjs/operator/map';
export class MockStore<T> extends BehaviorSubject<T> {
constructor(private _initialState: T) {
super(_initialState);
}
dispatch = (action: Action): void => {
}
select = <T, R>(pathOrMapFn: any, ...paths: string[]): Observable<R> => {
return map.call(this, pathOrMapFn);
}
}
Then you can provide the mockStore in you tests:
const initialState = {...};
TestBed.configureTestingModule({
...
providers:[
{provide:Store, useValue: new MockStore(initialState)}
]
...
})

Related

Angular NgModel two-way binding unit test

I'm attempting to test the two-way binding feature in Angular 2. I've also read through a few other answers but I still can't get the test to pass.
When the input field is updated, I would like to run a test that ensure the searchQuery property on the AppComponent class is the same as the value of the input field.
As mentioned, I've read a few other answers and as I've gone along included additional pieces of code. So what is there currently might not all be needed?
Component
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
template: '<input type="text" name="input" [(ngModel)]="searchQuery" (change)="onChange()" id="search">',
styles: ['']
})
export class AppComponent {
public searchQuery: string;
onChange() {
console.log(this.searchQuery);
}
}
Unit test
import { ComponentFixture, TestBed, async, fakeAsync, tick, ComponentFixtureAutoDetect } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { AppComponent } from './app.component';
import { FormsModule } from '#angular/forms';
describe('AppComponent', () => {
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AppComponent ],
providers: [],
imports: [ FormsModule ],
schemas: []
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
});
it('should create the app', fakeAsync(() => {
const de = fixture.debugElement.query(By.css("#search"));
const el = de.nativeElement;
el.value = "My string";
var event = new Event('input', {
'bubbles': true,
'cancelable': true
});
el.dispatchEvent(event);
tick();
fixture.detectChanges();
expect(comp.searchQuery).toEqual("My string");
}));
});
If there is a better approach, I am of course happy to get any feedback around this.
You have to run
fixture.detectChanges();
before dispatching event to ensure that your control has initialized and registered onChange event
setUpControl function
// view -> model
dir.valueAccessor.registerOnChange(function (newValue) {
dir.viewToModelUpdate(newValue);
control.markAsDirty();
control.setValue(newValue, { emitModelToViewChange: false });
});
Plunker Example
See also
Angular2 NgModel not getting value in Jasmine test

Angular2 DI error in jasmine test

I'm writing some angular2 unit tests using jasmine and working on a unit test for a component. For some reason, when I try to instantiate the component using the TestBed class, I get a dependency injection error.
LoginComponent:
import { Component } from '#angular/core';
import { LoginService } from './login.service';
#Component({
selector: 'login',
template: require('./login.template.pug'),
styles: [require('./style.scss')],
providers: [LoginService]
})
export class LoginComponent {
public username: string;
public password: string;
constructor(private loginService: LoginService) {
}
public login(username, password) {
this.loginService.login(username, password)
.then((token: string) => {
})
}
}
LoginComponentTest:
import { TestBed, inject, async, ComponentFixture } from '#angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { LoginComponent } from './login.component';
import { LoginService } from './login.service';
import { BrowserModule } from '#angular/platform-browser';
describe('LoginComponent', () => {
class MockClass {
get(url): Promise<any> {
throw Error('not implemented');
};
}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
LoginComponent
],
providers: [
LoginComponent,
{ provide: LoginService, useClass: MockClass },
],
imports: [
FormsModule,
BrowserModule,
ReactiveFormsModule
]
})
}));
it('will let user login', async(() => {
TestBed.compileComponents()
.then(() => {
let fixture: ComponentFixture<LoginComponent> = TestBed.createComponent(LoginComponent);
});
}));
});
The line
let fixture: ComponentFixture<LoginComponent> = TestBed.createComponent(LoginComponent);
gives the following error:
Chrome 57.0.2987 (Mac OS X 10.12.3) LoginComponent will let user login
FAILED Error: DI Error
at NoProviderError.ZoneAwareError (webpack:///~/zone.js/dist/zone.js:958:0 <-
config/spec-bundle.js:75553:33)
I have the LoginComponent declared along with its own dependency, LoginService.
Any ideas about what's going on?
Okay, I figured it out, kind of. Apparently the issue has to do with the providers declaration in LoginComponent:
providers: [LoginService]
Once I moved that out of LoginComponent and into AppModule, things started working. Would be interested to know why the test doesn't work with it declared inside the component itself.

"Can't resolve all parameters for MdDialogRef: (?)" Error when testing NG2 Material Dialog component

I have a login component as follows:
import { Component, OnInit } from '#angular/core';
import { MdDialogRef } from '#angular/material';
import { AuthService } from '../../core/services/auth.service';
#Component({
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginDialogComponent implements OnInit {
model: {
email: string,
password: string
};
error;
constructor(
private authService: AuthService,
private dialogRef: MdDialogRef<LoginDialogComponent>
) { }
ngOnInit() {
this.model = {
email: '',
password: ''
};
}
signin() {
this.error = null;
this.authService.login(this.model.email, this.model.password).subscribe(data => {
this.dialogRef.close(data);
}, err => {
this.error = err.json();
});
}
}
And I have a test spec for this component as follows:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { MdDialogRef, OverlayRef } from '#angular/material';
import { AuthService } from '../../core/services/auth.service';
import { LoginDialogComponent } from './login.component';
describe('Component: Login', () => {
let component: LoginDialogComponent;
let fixture: ComponentFixture<LoginDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
LoginDialogComponent
],
imports: [],
providers: [
AuthService,
MdDialogRef,
OverlayRef
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
I've tried a million different things, and no matter what I do, I get the following error:
Can't resolve all parameters for MdDialogRef: (?)
Here's the code for MdDialogRef, which only has 1 parameter, OverlayRef. What am I missing?
import { OverlayRef } from '../core';
import { Observable } from 'rxjs/Observable';
/**
* Reference to a dialog opened via the MdDialog service.
*/
export declare class MdDialogRef<T> {
private _overlayRef;
/** The instance of component opened into the dialog. */
componentInstance: T;
/** Subject for notifying the user that the dialog has finished closing. */
private _afterClosed;
constructor(_overlayRef: OverlayRef);
/**
* Close the dialog.
* #param dialogResult Optional result to return to the dialog opener.
*/
close(dialogResult?: any): void;
/** Gets an observable that is notified when the dialog is finished closing. */
afterClosed(): Observable<any>;
}
EDIT: taking a clue from #Ryan's comment, I tried removing the MdDialogRef provider entirely and got the following error:
Can't resolve all parameters for OverlayRef: (?, ?, ?)
This leads me to believe that the problem is actually w/MdDialogRef trying to resolve OverlayRef, not w/MdDialogRef itself.
WORKING EXAMPLE The code below is the actual working code, per Yurzui's suggestion.
/* tslint:disable:no-unused-variable */
import { NgModule } from '#angular/core';
import { async, TestBed } from '#angular/core/testing';
import { CommonModule } from '#angular/common';
import { FormsModule } from '#angular/forms';
import { MaterialModule, MdDialogModule, MdToolbarModule, MdDialog, MdDialogRef } from '#angular/material';
import { CoreModule } from '../../core/core.module';
import { LoginDialogComponent } from './login.component';
#NgModule({
declarations: [
LoginDialogComponent
],
entryComponents: [
LoginDialogComponent
],
exports: [
LoginDialogComponent
],
imports: [
CommonModule,
CoreModule,
FormsModule,
MaterialModule.forRoot(),
MdDialogModule.forRoot(),
MdToolbarModule.forRoot()
]
})
class LoginDialogSpecModule { }
describe('Component: Login Dialog', () => {
let component: LoginDialogComponent;
let dialog: MdDialog;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
LoginDialogSpecModule
]
});
});
beforeEach(() => {
dialog = TestBed.get(MdDialog);
let dialogRef = dialog.open(LoginDialogComponent);
component = dialogRef.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
There is an issue ComponentFactoryResolver is not aware of components compiled via TestBed
According to this problem angular2 team offers workaround by creating real module with entryComponents property
https://github.com/angular/material2/blob/2.0.0-beta.1/src/lib/dialog/dialog.spec.ts#L387-L402
So your test could write like this:
import { MdDialog, MdDialogModule } from '#angular/material';
#NgModule({
declarations: [TestComponent],
entryComponents: [TestComponent],
exports: [TestComponent],
})
class TestModule { }
describe('Component: Login', () => {
let component: TestComponent;
let dialog: MdDialog;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestModule, MdDialogModule]
});
});
beforeEach(() => {
dialog = TestBed.get(MdDialog);
let dialogRef = dialog.open(TestComponent);
component = dialogRef.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Plunker Example
I was getting the same error when running my code normally, i.e. I did not write a test case.
I found that the line provides: [ MdDialogRef ]in my main component was giving this exact same error, and everything worked without it.

"Error: No provider for router" while writing Karma-Jasmine unit test cases

We have done one angular2 project set up and inside that created one module (my-module) and inside that module created one component (my-new-component) using following cmd commands:
ng new angular2test
cd angular2test
ng g module my-module
ng generate component my-new-component
After creating the set up and all components, we ran ng test command from cmd inside angular2test folder.
The below file is our my-new-component.component.ts file:
import { Component, OnInit } from '#angular/core';
import { Router, Routes, RouterModule } from '#angular/router';
import { DummyService } from '../services/dummy.service';
#Component({
selector: 'app-my-new-component',
templateUrl: './my-new-component.component.html',
styleUrls: ['./my-new-component.component.css']
})
export class MyNewComponentComponent implements OnInit {
constructor(private router : Router, private dummyService:DummyService) { }
ngOnInit() {
}
redirect() : void{
//this.router.navigate(['/my-module/my-new-component-1'])
}
}
The below file is our my-new-component.component.spec.ts file:
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { RouterTestingModule } from '#angular/router/testing';
import {NgbModule} from '#ng-bootstrap/ng-bootstrap';
import { DummyService } from '../services/dummy.service';
import { MyNewComponentComponent } from './my-new-component.component';
describe('MyNewComponentComponent', () => {
let component: MyNewComponentComponent;
let fixture: ComponentFixture<MyNewComponentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule, NgbModule.forRoot(), DummyService],
declarations: [ MyNewComponentComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyNewComponentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
We are getting the below cmd error while running the ng test command:
Chrome 54.0.2840 (Windows 7 0.0.0): Executed 1 of 1 (1 FAILED) ERROR (0.593 secs / 2.007 secs)
Chrome 54.0.2840 (Windows 7 0.0.0) MyNewComponentComponent should create FAILED
Failed: Unexpected value 'DummyService' imported by the module 'DynamicTestModule'
Error: Unexpected value 'DummyService' imported by the module 'DynamicTestModule'
We have updated the component file and the spec file. Pleased find below the code snippet.
The below file is our my-new-component.component.ts file:
import { Component, OnInit } from '#angular/core';
import { Router, Routes, RouterModule } from '#angular/router';
import { DummyService } from '../services/dummy.service';
#Component({
selector: 'app-my-new-component',
templateUrl: './my-new-component.component.html',
styleUrls: ['./my-new-component.component.css']
})
export class MyNewComponentComponent implements OnInit {
constructor(private router : Router, private dummyService:DummyService, public fb: FormBuilder) {
super(fb);
}
ngOnInit() {
}
redirect() : void{
//this.router.navigate(['/my-module/my-new-component-1'])
}
}
The below file is our my-new-component.component.spec.ts file:
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { FormsModule, FormGroup, FormBuilder, Validators, ReactiveFormsModule} from '#angular/forms';
import { SplitPipe } from '../../common/pipes/string-split.pipe';
import { RouterTestingModule } from '#angular/router/testing';
import { DummyService } from '../services/dummy.service';
import { MyNewComponentComponent } from './my-new-component.component';
describe('MyNewComponentComponent', () => {
let component: MyNewComponentComponent;
let fixture: ComponentFixture<MyNewComponentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule, DummyService ,HttpModule, FormBuilder],
declarations: [ MyNewComponentComponent, SplitPipe]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyNewComponentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
But while running the ng test command, we are getting the below error.
09 12 2016 09:13:48.987:WARN [karma]: No captured browser, open http://localhost:9876/
09 12 2016 09:13:49.008:INFO [karma]: Karma v1.2.0 server started at http://localhost:9876/
09 12 2016 09:13:49.010:INFO [launcher]: Launching browser Chrome with unlimited concurrency
09 12 2016 09:13:49.420:INFO [launcher]: Starting browser Chrome
09 12 2016 09:13:58.642:INFO [Chrome 54.0.2840 (Windows 7 0.0.0)]: Connected on socket /#QZ9LSSUVeK6KwNDlAAAA with id 46830907
Failed: Unexpected value 'FormBuilder' imported by the module 'DynamicTestModule'
Error: Unexpected value 'FormBuilder' imported by the module 'DynamicTestModule'
You need to import RouterTestingModule when setting up the test module.
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { RouterTestingModule } from '#angular/router/testing';
import { MyNewComponentComponent } from './my-new-component.component';
describe('MyNewComponentComponent', () => {
let component: MyNewComponentComponent;
let fixture: ComponentFixture<MyNewComponentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [ MyNewComponentComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyNewComponentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Edit: example with mock DummyService
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { RouterTestingModule } from '#angular/router/testing';
import { MyNewComponentComponent } from './my-new-component.component';
// import the service
import { DummyService } from '../dummy.service';
// mock the service
class MockDummyService extends DummyService {
// mock everything used by the component
};
describe('MyNewComponentComponent', () => {
let component: MyNewComponentComponent;
let fixture: ComponentFixture<MyNewComponentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [MyNewComponentComponent],
providers: [{
provide: DummyService,
useClass: MockDummyService
}]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MyNewComponentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Add RouterTestingModule for configureTestingModule testCase
==> imports: [RouterTestingModule],
import {RouterTestingModule} from '#angular/router/testing';
beforeEach(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule], // <====
providers: [],
declarations: [],
});
});
I get the same kind of error and I want to share my solution to help others
The Error I get in Karma
error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'RouterModule', 'Router', 'Function', 'Function' ] })
NullInjectorError: R3InjectorError(DynamicTestModule)[RouterModule -> Router -> Function -> Function]:
NullInjectorError: No provider for Function!
inventory-view.component.ts
#Component({
selector: 'app-inventory-view',
templateUrl: './inventory-view.component.html',
styleUrls: ['./inventory-view.component.scss'],
animations: []
})
export class InventoryViewComponent implements OnInit, AfterViewInit, OnDestroy {
constructor(
public router: Router, // <--- here was the problem
public activatedRoute: ActivatedRoute
) { }
In my test file
inventory-view.component.spec.ts
import { HttpClientModule } from '#angular/common/http';
import { waitForAsync, ComponentFixture, TestBed } from '#angular/core/testing';
import { RouterTestingModule } from '#angular/router/testing';
import { ActivatedRoute, convertToParamMap, Router } from '#angular/router';
const ActivatedRouteSpy = {
snapshot: {
paramMap: convertToParamMap({
some: 'some',
else: 'else',
})
},
queryParamMap: of(
convertToParamMap({
some: 'some',
else: 'else',
})
)
};
const RouterSpy = jasmine.createSpyObj(
'Router',
['navigate']
);
describe('InventoryViewComponent', () => {
let component: InventoryViewComponent;
let fixture: ComponentFixture<InventoryViewComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
HttpClientModule,
RouterTestingModule,
],
declarations: [ InventoryViewComponent ],
providers: [
{ provide: ActivatedRoute, useValue: ActivatedRouteSpy },
{ provide: Router, useValue: RouterSpy }
]
})
.compileComponents();
}));
beforeEach(waitForAsync(() => {
fixture = TestBed.createComponent(InventoryViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});

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

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');
});
}
}
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 ]
})
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.
If you want only isolated test and DO NOT CARE about template,you can add NO_ERRORS_SCHEMA. This tells Angular not to show error if it encounters any unknown attribute or element in HTML
Eg:
TestBed.configureTestingModule({
declarations: [ ComponentHeaderComponent ],
schemas: [ NO_ERRORS_SCHEMA ]
})