Mocking a parent FormGroup via #input in Jasmine - unit-testing

So I have a child component that goes something like this
export class ChildComponent implements OnInit {
#Input('parentForm')
public parentForm: FormGroup;
constructor(private fb: FormBuilder, private cd: ChangeDetectorRef) { }
ngOnInit() {
this.parentForm.addControl('newControl', <Some Control>);
}
}
Next I have a barebones unit testing file that goes like this
describe('ChildComponent', () => {
let component: ChildComponent;
let fixture: ComponentFixture<ChildComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, FormsModule],
declarations: [ ChildComponent ],
providers: [ FormBuilder, FormGroup ]
})
.compileComponents();
}));
beforeEach(inject([FormBuilder], (fb: FormBuilder) => {
fixture = TestBed.createComponent(ChildComponent);
component = fixture.componentInstance;
component.parentForm = fb.group({});
component.ngOnInit();
fixture.detectChanges();
}));
fit('should be created', () => {
expect(component).toBeTruthy();
});
});
Previously I had an issue where parentForm was undefined so I tried to build it myself by doing injecting FormBuilder in the beforeEach by doing this component.parentForm = fb.group({});. However now the issue is that karma/jasmine cannot find FormBuilder
Cannot find name 'FormBuilder'.
All I am trying to do is try to get or mock the parentForm for when I create an instance of the component during my unit testing, and I need it because I am calling ngOnInit during the for each as it as a new control.
Any ideas. Thank you

I was able to setup a successful Karma spec test for a Reactive Form Parent <-> Child component. Hopefully the example below will help guide your setup. I've simplified as much code from my codebase to focus on the core question you're trying to resolve.
Parent Component
parent.component.html
...
<div [stepControl]="childFormGroup">
<child-form-group [formGroup]="childFormGroup"></child-form-group>
</div>
...
parent.component.ts
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators, FormControl } from '#angular/forms';
#Component({
selector: 'form-parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.scss']
})
export class FormParentComponent implements OnInit {
// childFormGroup will be available on the parent DOM
// so we can inject it / pass it to the ChildFormComponent
public childFormGroup : FormGroup;
constructor(private _formBuilder: FormBuilder) {
this.createForm();
}
private createForm() : void {
this.childFormGroup = this._formBuilder.group({
name: ['Sample Name', Validators.required],
email: ['', Validators.required]
});
}
}
Child Component
child.component.html
...
<form [formGroup]="formGroup">
<p>This is the childFormGroup</p>
<br>
<div>
<input placeholder="Name"
formControlName="name"
autocomplete="off">
</div>
<div>
<input placeholder="Email"
formControlName="email"
autocomplete="off">
</div>
</form>
...
child.component.ts
import { Component, Input, Output, EventEmitter } from '#angular/core';
import { FormGroup } from '#angular/forms';
#Component({
selector: 'child-form-group',
templateUrl: './child.component.html',
styleUrls: ['./child.component.scss'],
})
export class ChildFormComponent {
// This declares an inherited model available to this component
#Input() formGroup : FormGroup;
constructor() { }
/* There is no need to create the formGroup here
hence no constructor method call or ngOnInit() hook...
It will simply inherit the formGroup by passing it as an
attribute on the DOM from parent.component.html
*/
}
child.component.spec.ts
import { async, ComponentFixture, TestBed, inject } from '#angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { FormsModule, ReactiveFormsModule, FormGroup, FormBuilder, Validators } from '#angular/forms';
import { ChildFormComponent } from './child.component';
describe('ChildFormComponent', () => {
let component: ChildFormComponent;
let fixture: ComponentFixture<ChildFormComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
imports: [
FormsModule,
ReactiveFormsModule
],
declarations: [
ChildFormComponent
]
})
.compileComponents();
}));
beforeEach(inject([FormBuilder], (fb: FormBuilder) => {
fixture = TestBed.createComponent(Step2Component);
component = fixture.componentInstance;
/* This is where we can simulate / test our component
and pass in a value for formGroup where it would've otherwise
required it from the parent
*/
component.formGroup = fb.group({
name: ['Other Name', Validators.required],
email: ['', Validators.required]
});
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});

Related

Unit testing and mocking a service with DI

I have been struggling with this for a while, and I'm hoping someone can help. I have a component that uses a service to get data. I am attempting to add unit tests to it. My problem is that the tests always fail with "Error: No provider for Http". Here is my code:
Service:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/add/operator/map';
import { Contact } from './contact.model';
#Injectable()
export class ContactsService {
constructor(private http: Http) { }
public getContacts(): Observable<Array<Contact>> {
return this.http.get('assets/contacts.json').map(res => {
let r = res.json().Contacts;
return r;
});
}
}
Component:
import { Component, OnInit, NgModule } from '#angular/core';
import { FormsModule } from '#angular/forms';
import { Contact } from '../contact.model';
import { ContactsService } from '../contacts.service';
#Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.css'],
providers: [ContactsService]
})
export class ContactsComponent implements OnInit {
contactsAll: Array<Contact>;
contacts: Array<Contact>;
constructor(private contactsService: ContactsService) { }
ngOnInit() {
this.contactsService.getContacts().subscribe((x) => {
this.contactsAll = x;
this.contacts = this.contactsAll;
});
}
}
Tests:
import { async, ComponentFixture, TestBed, inject } from '#angular/core/testing';
import { FormsModule } from '#angular/forms';
import { By } from '#angular/platform-browser';
import { Observable } from 'rxjs/Rx';
import { ContactsComponent } from './contacts.component';
import { ContactsService } from '../contacts.service';
import { Contact } from '../contact.model';
class MockContactsService extends ContactsService {
constructor() {
super(null);
}
testContacts: Array<Contact> = [
new Contact("test1 mock", 12345, 10000),
new Contact("test2 mock", 23456, 20000)
];
public getContacts(): Observable<Array<Contact>> {
return Observable.of(this.testContacts);
}
}
describe('ContactsComponent', () => {
let component: ContactsComponent;
let fixture: ComponentFixture<ContactsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [ContactsComponent],
// providers: [{ provide: ContactsService, useClass: MockContactsService }] // this is needed for the service mock
}).overrideComponent(ContactsService, {// The following is to override the provider in the #Component(...) metadata
set: {
providers: [
{ provide: ContactsService, useClass: MockContactsService },
]
}
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ContactsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('1st test', () => {
it('true is true', () => expect(true).toBe(true));
})
});
Let's try this:
First, move your providers array from your component to your NgModule. It's better to provide your services at the module level since it de-couples your providers from your component tree structure (unless you specifically want to have a separate instance of a provider per component, and from your simplified use case, there's no need for a separate instance per component).
so,
#Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.css'],
/// providers: [ContactsService] <-- remove this line
})
export class ContactsComponent implements OnInit {
.....
and add it to the NgModule that declares your ContactsComponent
#NgModule({
imports: ..
declarations: ...
providers: [ContactsService] // <-- provider definition moved to here
})
export class ModuleDeclaringContactsComponent
Once you do that, then mocking the ContactsService in your test is easy to implement.
TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [ContactsComponent],
providers: [{ provide: ContactsService, useClass: MockContactsService }] // this is needed for the service mock
});
With that, you should be good to go.
Sorry everyone - turns out it was something completely different.
I modified my code as per snorkpete's answer, and I am going to mark that as the answer, as I believe that is the cleanest approach.
The real problem came from using Angular Cli to create my project. It automatically created tests for my component and my service. This meant the code in the service test was causing the error, not the code in the component. I commented out the code in the service test and everything passed.
Annoyingly, there was no indication in any of the failures that this is where the error was coming from!
In case if component should have Service in providers we can ovverride metadata by TestBed.overrideComponent():
#Component({
selector: 'app-contacts',
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.css'],
providers: [ContactsService] // IF YOU NEED IT
})
we need to do next:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
declarations: [ SomeComponent ],
providers: [
{provide: SomeService, useValue: SomeMock},
provideMockStore({system: {userConfig: {viewSettings: {theme: ThemeName.light}}}} as any)
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
TestBed.overrideComponent(SomeComponent , { set: { providers: [{provide: SomeService, useValue: SomeMock}]}})
}));
beforeEach(() => {
fixture = TestBed.createComponent(SomeComponent );
component = fixture.componentInstance;
fixture.detectChanges();
});
more info here https://codecraft.tv/courses/angular/unit-testing/dependency-injection/

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

routeLink not rendered coorrectly while testing

I have header component definition as following:
import { Component, OnChanges, Input } from '#angular/core';
#Component({
selector: 'app-section-header',
template:`
<div class="pageTitle">
<h1>{{name}}</h1>
<a class="editBtn" [routerLink]="routerLink">edit</a>
</div>
<div class="progress"></div>
`,
styleUrls: ['./section-header.component.css']
})
export class SectionHeaderComponent implements OnChanges {
public routerLink: string[];
#Input() name: string;
ngOnChanges() {
this.routerLink = ['/section', this.name, 'edit'];
}
}
this component gets binding 'name' from its parent component, later it used to form a part of routeLink to 'edit' screen.
It is working well when running application.
For some reason, I cannot test the correct creation of this link:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { SectionHeaderComponent } from './section-header.component';
import { RouterTestingModule } from '#angular/router/testing';
import { Component, Input, Injectable, OnChanges , SimpleChanges, Output,SimpleChange, EventEmitter} from '#angular/core'
fdescribe('SectionHeaderComponent', () => {
let component: SectionHeaderComponent;
let fixture: ComponentFixture<SectionHeaderComponent>;
let element, de;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [SectionHeaderComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SectionHeaderComponent);
component = fixture.componentInstance;
element = fixture.nativeElement; // to access DOM element
de = fixture.debugElement;
});
it('should create link to edit view', () => {
component.name='sasha';
fixture.detectChanges();
component.ngOnChanges();
fixture.whenStable().then(() => {
expect(element.querySelector('h1').innerText).toBe('Sasha');
//for some reason this test failing with error expected '/'is not
// equal to 'section/sasha/edit'
expect(de.query(By.css('a')).nativeElement.getAttribute('href')).toBe ('/section/sasha/edit');
});
});
});
Where am I go wrong?
Thanks
You need to call fixture.detectChanges() after the call to ngOnChanges(). After making this change, it should work
Plunker
it('should create link to edit view', () => {
component.name = 'sasha';
component.ngOnChanges();
fixture.detectChanges()
expect(element.querySelector('h1').innerText).toBe('sasha');
expect(de.query(By.css('a')).nativeElement.getAttribute('href'))
.toBe('/section/sasha/edit');
});

Angular 2 Testing Component Gives "Error: Uncaught (in promise): Error: Template parse errors"

I've recently made an ng2 (using 2.0.1) app with multiple components and services. I'm in the middle of testing (Karma Jasmine) my HeaderComponent which contains my UserService (Which uses an extended Http class).
I've replicated simple tests from the Angular.io Docs to spy on a service and wait for after component initialization to check if the service function has been fired and its content. Every time I run the last test using fakeAsync (and async), which checks the content of the currentUser variable in header.component, I receive the following error...
Error: Uncaught (in promise): Error: Template parse errors:
'header-section' is not a known element:
1. If 'header-section' is an Angular component, then verify that it is part of this module.
2. If 'header-section' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component to suppress this message. ("
[ERROR ->]<header-section></header-section>
<router-outlet></router-outlet>
<footer-section></footer-sectio"): AppComponent#1:2
'router-outlet' is not a known element:
1. If 'router-outlet' is an Angular component, then verify that it is part of this module.
2. If 'router-outlet' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component to suppress this message. ("
<header-section></header-section>
[ERROR ->]<router-outlet></router-outlet>
<footer-section></footer-section>"): AppComponent#2:2
'footer-section' is not a known element:
1. If 'footer-section' is an Angular component, then verify that it is part of this module.
2. If 'footer-section' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component to suppress this message. ("
<header-section></header-section>
<router-outlet></router-outlet>
[ERROR ->]<footer-section></footer-section>"): AppComponent#3:2
These selectors are from my AppComponent...
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'my-app',
moduleId: module.id,
template: `
<header-section></header-section>
<router-outlet></router-outlet>
<footer-section></footer-section>`
})
export class AppComponent {
constructor() {}
test(): string {
return 'this is a test';
}
}
My header.component.spec...
import { Http, Request, RequestOptionsArgs, Response, XHRBackend, RequestOptions, ConnectionBackend, Headers } from '#angular/http';
import { HttpIntercept } from '../../services/auth/auth.service';
import { BrowserModule } from '#angular/platform-browser';
import { HttpModule, JsonpModule } from '#angular/http';
import { FormsModule } from '#angular/forms';
import { RouterTestingModule } from "#angular/router/testing";
import { appRoutes } from '../../routes';
import { Cookie } from 'ng2-cookies/ng2-cookies';
import { AppComponent } from '../app/app.component';
import { HeaderComponent } from './header.component';
import { FooterComponent } from '../footer/footer.component';
import { HomeComponent } from '../home/home.component';
import { Four0FourComponent } from '../404/four0four.component';
import { UserProfileComponent } from '../user-profile/user-profile.component';
import { UserService } from '../../services/user/user.service';
import { ClockService } from '../../services/clock/clock.service';
import { Observable } from 'rxjs/Observable';
import { TestBed, async, fakeAsync, tick } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { User } from '../../models/user/user.model';
class MockRouter { public navigate() { }; }
describe('HeaderComponent Test', () => {
let fixture;
let comp;
let userService;
let spy;
let user = new User({
_id: 123456,
userName: 'testName',
firstName: 'testFirst',
lastName: 'testLast',
email: 'test#email.com',
create: 'now',
role: 'user'
});
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
BrowserModule,
HttpModule,
FormsModule,
JsonpModule,
RouterTestingModule.withRoutes(appRoutes)
],
declarations: [
HomeComponent,
UserProfileComponent,
Four0FourComponent,
FooterComponent,
HeaderComponent,
AppComponent
],
providers: [
{
provide: Http,
useFactory: (
backend: XHRBackend,
defaultOptions: RequestOptions) =>
new HttpIntercept(backend, defaultOptions),
deps: [XHRBackend, RequestOptions]
},
Cookie
]
});
fixture = TestBed.createComponent(HeaderComponent);
comp = fixture.componentInstance;
userService = fixture.debugElement.injector.get(UserService);
spy = spyOn(userService, 'getMe')
.and.returnValue(Observable.of(user));
});
it('should instantiate component', () => {
expect(fixture.componentInstance instanceof HeaderComponent).toBe(true);
});
it('should not show currentUser before OnInit', () => {
expect(spy.calls.any()).toBe(false, 'getMe not yet called');
});
it('should still not show currentUser after component initialized', () => {
// Set cookie token, for the getMe to call
Cookie.set('token', 'test_token_alpha');
fixture.detectChanges();
expect(spy.calls.any()).toBe(true, 'getMe called');
});
//The problem test is bellow
it('should show currentUser after getMe promise', fakeAsync(() => {
fixture.detectChanges();
tick();
fixture.detectChanges();
expect(comp.currentUser).toEqual(user);
}));
});
Here's my header.component...
import { Component } from '#angular/core';
import { Cookie } from 'ng2-cookies/ng2-cookies';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';
import { UserService } from '../../services/user/user.service';
import { ClockService } from '../../services/clock/clock.service';
import { User } from '../../models/user/user.model';
#Component({
selector: 'header-section',
providers: [
UserService,
ClockService
],
moduleId: module.id,
template: `
<style>
header{
background: rgb(55, 129, 215);
position: relative;
}
.user-sign{
position: absolute;
top:0;
right:0;
margin: 23px 5%;
}
.app-title{
font-family: cursive;
padding: 15px;
text-align: center;
font-size: 36px;
color: white;
}
.user-sign button:hover{
cursor: pointer;
}
.active{
color: orange;
}
</style>
<header>
<a routerLink='/' routerLinkActive='active'>Home</a>
<a routerLink='/profile' routerLinkActive='active'>Profile</a>
<a routerLink='/yoloswaq69420blazeitfgt' routerLinkActive='active'>404</a>
<div class='user-sign'>
<h3 *ngIf='currentUser'>Welcome, {{currentUser.userName}}</h3>
<button *ngIf='!currentUser' type='button' (click)='testRegisterUser()'>Sign up</button>
<button *ngIf='!currentUser' type='button' (click)='testUser()'>Sign in</button>
<button type='button' (click)='logout()'>Sign out</button>
</div>
<h1 class='app-title'>MEA2N Fullstack</h1>
</header>`
})
export class HeaderComponent {
errorMessage: string;
public currentUser: User;
clock = this.clockService.currentTime;
constructor(private userService: UserService, private clockService: ClockService) { }
ngOnInit() {
let token = Cookie.get('token');
if (token)
this.userService.getMe().subscribe(user => this.currentUser = user);
}
login(email: string, password: string) {
this.userService.login(email, password)
.subscribe(() => {
return this.userService.getMe()
.subscribe(user => {
this.currentUser = user;
})
});
}
logout() {
this.userService.logout();
this.currentUser = null;
}
registerUser(username: string, email: string, password: string) {
this.userService.signup(username, email, password)
.subscribe(() => {
return this.userService.getMe()
.subscribe(user => {
this.currentUser = user;
})
});
}
testUser() {
this.login('jc.thomas4214#gmail.com', 'flight1855');
}
testRegisterUser() {
this.registerUser('Jason', 'jc.thomas4214#gmail.com', 'flight1855');
}
}
I've suspected that this error is occurring because of how I'm initializing my TestBed.configureTestingModule().
I've tried...
Reordering both app.module and TestBed module declarations
adding schema: [CUSTOM_ELEMENTS_SCHEMA] to both modules
Since you are unit testing the Header component in this case, there is no need to include other modules and components
TestBed can look like this:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
AppModule
],
providers: [
{provide: UserService, useClass: MockUserService},
{provide: ClockService, useClass: MockClockService}
]
});
fixture = TestBed.createComponent(HeaderComponent);
});
In this example the Services have been mocked but they can be spied too as you have correctly done for the UserService

Angular2 - Testing ngOninit in Components

I have a listing component with following code:
///<reference path="../../node_modules/angular2/typings/browser.d.ts"/>
import { Component, OnInit } from 'angular2/core';
import { ROUTER_DIRECTIVES } from 'angular2/router';
import { Employee } from '../models/employee';
import { EmployeeListServiceComponent } from '../services/employee-list-service.component';
#Component({
selector: 'employee-list',
template: `
<ul class="employees">
<li *ngFor="#employee of employees">
<a [routerLink]="['EmployeeDetail', {id: employee.id}]">
<span class="badge">{{employee.id}}</span>
{{employee.name}}
</a>
</li>
</ul>
`,
directives: [ROUTER_DIRECTIVES],
providers: [EmployeeListServiceComponent]
})
export class EmployeeListComponent implements OnInit {
public employees: Employee[];
public errorMessage: string;
constructor(
private _listingService: EmployeeListServiceComponent
){}
ngOnInit() {
this._listingService.getEmployees().subscribe(
employees => this.employees = employees,
error => this.errorMessage = <any>error
);
}
}
I wish to write unit tests for the ngOninit hook. I have written following test:
/// <reference path="../../typings/main/ambient/jasmine/jasmine.d.ts" />
import {
it,
describe,
expect,
TestComponentBuilder,
injectAsync,
setBaseTestProviders,
beforeEachProviders,
} from "angular2/testing";
import { Component, provide, ApplicationRef, OnInit } from "angular2/core";
import {
TEST_BROWSER_PLATFORM_PROVIDERS,
TEST_BROWSER_APPLICATION_PROVIDERS
} from "angular2/platform/testing/browser";
import {
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
ROUTER_PRIMARY_COMPONENT,
APP_BASE_HREF
} from 'angular2/router';
import {XHRBackend, HTTP_PROVIDERS} from "angular2/http";
import { MockApplicationRef } from 'angular2/src/mock/mock_application_ref';
import {MockBackend } from "angular2/src/http/backends/mock_backend";
import {Observable} from 'rxjs/Rx';
import 'rxjs/Rx';
import { Employee } from '../models/employee';
import { EmployeeListComponent } from './list.component';
import { EmployeeListServiceComponent } from '../services/employee-list-service.component';
class MockEmployeeListServiceComponent {
getEmployees () {
return Observable.of([
{
"id": 1,
"name": "Abhinav Mishra"
}
]);
}
}
#Component({
template: '<employee-list></employee-list>',
directives: [EmployeeListComponent],
providers: [MockEmployeeListServiceComponent]
})
class TestMyList {}
describe('Employee List Tests', () => {
setBaseTestProviders(TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS);
beforeEachProviders(() => {
return [
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(EmployeeListServiceComponent, {useClass: MockEmployeeListServiceComponent}),
provide(XHRBackend, {useClass: MockBackend}),
provide(APP_BASE_HREF, {useValue: '/'}),
provide(ROUTER_PRIMARY_COMPONENT, {useValue: EmployeeListComponent}),
provide(ApplicationRef, {useClass: MockApplicationRef})
]
});
it('Should be true',
injectAsync([TestComponentBuilder], (tcb) => {
return tcb
.createAsync(TestMyList)
.then((fixture) => {
fixture.detectChanges();
var compiled = fixture.debugElement.nativeElement;
console.log(compiled.innerHTML);
expect(true).toBe(true);
});
})
);
});
However, the output of console.log in the test is an empty ul tag as follows:
'<employee-list>
<ul class="employees">
<!--template bindings={}-->
</ul>
</employee-list>'
Can anyone suggest me the proper way of writing unit tests for component hooks?
SOLUTION
Mock the http request in the injectAsync block as follows:
backend.connections.subscribe(
(connection:MockConnection) => {
var options = new ResponseOptions({
body: [
{
"id": 1,
"name": "Abhinav Mishra"
}
]
});
var response = new Response(options);
connection.mockRespond(response);
}
);
However now i am getting another error as follows:
Failed: EXCEPTION: Component "EmployeeListComponent" has no route config. in [['EmployeeDetail', {id: employee.id}] in EmployeeListComponent#3:7]
ORIGINAL EXCEPTION: Component "EmployeeListComponent" has no route config.
ORIGINAL STACKTRACE:
Error: Component "EmployeeListComponent" has no route config.
If you call async code in ngOnInit() you can't assume it is completed when console.log(...) is executed. this.employees is only set when the callback you passed to subscribe(...) gets called after the response arrived.
If you use MockBackend you can control the response and after the response was passed you have to run fixture.detectChanges() again to make the component re-render with the updated data, then you can read innerHTML and expect it to contain the rendered content.