routeLink not rendered coorrectly while testing - unit-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');
});

Related

ngrx testing: Uncaught TypeError: Cannot read property 'xxxx' of undefined thrown

afterEach(() => {
fixture.destroy();
});I am currently trying to write tests for my ngrx based angular 7 application. The problem is that my test fails with the error Uncaught TypeError: Cannot read property 'xxxx' of undefined thrown. Here's how my test file looks like.
explore-products.component.spec.ts
import { async, ComponentFixture, TestBed } from "#angular/core/testing";
import { ExploreProductsComponent } from "./explore-products.component";
import { provideMockStore, MockStore } from "#ngrx/store/testing";
import { IAppState } from "src/app/store/state/app.state";
import { Store, StoreModule } from "#ngrx/store";
import { appReducers } from "src/app/store/reducers/app.reducer";
describe("ExploreProductsComponent", () => {
let component: ExploreProductsComponent;
let fixture: ComponentFixture<ExploreProductsComponent>;
let store: MockStore<IAppState>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ExploreProductsComponent],
providers: [provideMockStore()],
imports: [StoreModule.forRoot(appReducers)]
});
store = TestBed.get(Store);
});
beforeEach(() => {
fixture = TestBed.createComponent(ExploreProductsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
});
The only should create test is throwing the error. The error is being throwing by the selector somehow, which means the xxxx property is not initialized but I am not sure how to resolve it. Here's what my component looks like.
explore-products.component.ts
import { Component, OnInit, OnDestroy } from "#angular/core";
import { IProduct } from "src/app/models/product";
import { environment } from "src/environments/environment";
import { selectProducts } from "../../store/selectors/product";
import { Store, select } from "#ngrx/store";
import { IAppState } from "src/app/store/state/app.state";
import { Subscription } from "rxjs";
#Component({
selector: "app-explore-products",
templateUrl: "./explore-products.component.html",
styleUrls: ["./explore-products.component.css"]
})
export class ExploreProductsComponent implements OnInit, OnDestroy {
public productsLoading = true;
public endpoint = environment.apiEndpoint;
private productsSelector = this.store.pipe(select(selectProducts));
public products: IProduct[];
private subscriptionsArr: Subscription[] = [];
constructor(private store: Store<IAppState>) {}
ngOnInit() {
this.subscriptions();
}
subscriptions() {
const subcriberProduct = this.productsSelector.subscribe(products => {
this.products = products;
if (this.products !== null) {
this.toggleLoadingSign(false);
}
});
this.subscriptionsArr.push(subcriberProduct);
}
toggleLoadingSign(toggleOption: boolean) {
this.productsLoading = toggleOption;
}
ngOnDestroy() {
for (const subscriber of this.subscriptionsArr) {
subscriber.unsubscribe();
}
}
}
Please let me know if I can provide any other information.
Update
The problem is with AppState. The error is thrown since the state is not initialized which causes the error to occur i.e state.xxxx is undefined. The error sometimes randomly doesn't occur. I am not sure how to fix this.
The same problem is also mentioned here. But no solution
For me adding this line of code afterEach(() => { fixture.destroy(); }); in all my spec files injecting provideMockStore() fixed this issue intermittently triggering.
describe('Component', () => {
let component: Component;
let fixture: ComponentFixture<Component>;
beforeEach(async(() => {
const state = { featureKey: { property: value } }; // The state of your feature snippet
TestBed.configureTestingModule({
providers: [
provideMockStore({ initialState: state }),
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => { fixture.destroy(); });
it('should create', () => {
expect(component).toBeTruthy();
});
});
For me adding:
afterEach(() => {
fixture.destroy();
});
to every spec file of a component solved the issue.
You can try something like this.
See how I've created mock store and used it. Added single line comments (with ************) wherever code was updated:
import { async, ComponentFixture, TestBed } from "#angular/core/testing";
import { ExploreProductsComponent } from "./explore-products.component";
import { provideMockStore, MockStore } from "#ngrx/store/testing";
import { IAppState } from "src/app/store/state/app.state";
import { Store, StoreModule } from "#ngrx/store";
import { appReducers } from "src/app/store/reducers/app.reducer";
describe("ExploreProductsComponent", () => {
let component: ExploreProductsComponent;
let fixture: ComponentFixture<ExploreProductsComponent>;
//Update the store def.************
let store: MockStore<any>;
beforeEach( async(() => { //*****************UPDATE
TestBed.configureTestingModule({
declarations: [ExploreProductsComponent],
providers: [provideMockStore()],
//Change to imports************
imports: [StoreModule.forRoot({})]
}).compileComponents();//*****************UPDATE
//Removed this
//store = TestBed.get(Store);************
}));//*****************UPDATE
beforeEach(() => {
fixture = TestBed.createComponent(ExploreProductsComponent);
//Get store instance************
store = fixture.debugElement.injector.get(Store);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
//Spy on store actions************
const spy = spyOn(store, 'dispatch');
//Test is store is called properly************
expect(spy).toHaveBeenCalledWith(Your params)
expect(component).toBeTruthy();
});
});

Mocking a parent FormGroup via #input in Jasmine

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();
});
});

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

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 ]
})

Angular 2 unit testing data passed from parent component to child component

I have a question about the way I've seen (the very few) examples of testing of data passed down from a parent component into a child component. Currently, in the Angular2 docs, they're testing to see if data has been passed down from a parent component to a child by inspecting the dom values of the child component. The issue that I have with this approach is that it forces the parent's spec to know the html structure of the child component. The parent component's job is just to pass data into the child. An example...
I have a Story Component as follows:
'use strict';
import {Component, OnInit, Input} from '#angular/core';
import {StoryService} from '../../services/story.service';
import {StoryModel} from '../../models/story-model';
import {AlbumCover} from './album-cover/album-cover';
import {Author} from "./author/author";
import {StoryDuration} from "./story-duration/story-duration";
#Component({
selector: 'story',
templateUrl: 'build/components/story/story.html',
providers: [StoryService],
directives: [AlbumCover, Author, StoryDuration]
})
export class Story implements OnInit {
#Input('id') id:number;
public story:StoryModel;
constructor(private storyService:StoryService) {}
ngOnInit() {
this.getStory();
}
private getStory() {
this.storyService.getStory(this.id).subscribe(story => this.story = story);
}
}
Notice how it has an AlbumCover Component dependency in the directives array in the #Component decorator.
Here is my Story template:
<div *ngIf="story">
<album-cover [image]="story.albumCover" [title]="story.title"></album-cover>
<div class="author-duration-container">
<author [avatar]="story.author.avatar" [name]="story.author.name"></author>
<story-duration [word-count]="story.wordCount"></story-duration>
</div>
</div>
Notice the <album-cover [image]="story.albumCover" [title]="story.title"></album-cover> line where I'm binding the story.albumCover from the Story controller to the image property of the AlbumCover. This is all working perfectly. Now for the test:
import {provide} from '#angular/core';
import {beforeEach, beforeEachProviders, describe, expect, injectAsync, it, setBaseTestProviders, resetBaseTestProviders} from '#angular/core/testing';
import {HTTP_PROVIDERS} from '#angular/http';
import {BROWSER_APP_DYNAMIC_PROVIDERS} from "#angular/platform-browser-dynamic";
import {TEST_BROWSER_STATIC_PLATFORM_PROVIDERS, ADDITIONAL_TEST_BROWSER_PROVIDERS} from '#angular/platform-browser/testing';
import {ComponentFixture, TestComponentBuilder} from '#angular/compiler/testing';
import {Observable} from 'rxjs/Observable';
// TODO: this pattern of importing 'of' can probably go away once rxjs is fixed
// https://github.com/ReactiveX/rxjs/issues/1713
import 'rxjs/add/observable/of';
resetBaseTestProviders();
setBaseTestProviders(
TEST_BROWSER_STATIC_PLATFORM_PROVIDERS,
[BROWSER_APP_DYNAMIC_PROVIDERS, ADDITIONAL_TEST_BROWSER_PROVIDERS]
);
import {Story} from './story';
import {StoryModel} from '../../models/story-model';
import {StoryService} from '../../services/story.service';
var mockStory = {
id: 1,
title: 'Benefit',
albumCover: 'images/placeholders/story-4.jpg',
author: {
id: 2,
name: 'Brett Beach',
avatar: 'images/placeholders/author-1.jpg'
},
wordCount: 4340,
content: '<p>This is going to be a great book! I <strong>swear!</strong></p>'
};
class MockStoryService {
public getStory(id):Observable<StoryModel> {
return Observable.of(mockStory);
}
}
describe('Story', () => {
var storyFixture,
story,
storyEl;
beforeEachProviders(() => [
HTTP_PROVIDERS
]);
beforeEach(injectAsync([TestComponentBuilder], (tcb:TestComponentBuilder) => {
return tcb
.overrideProviders(Story, [
provide(StoryService, {
useClass: MockStoryService
})
])
.createAsync(Story)
.then((componentFixture:ComponentFixture<Story>) => {
storyFixture = componentFixture;
story = componentFixture.componentInstance;
storyEl = componentFixture.nativeElement;
componentFixture.detectChanges();
});
}));
describe(`ngOnInit`, () => {
describe(`storyService.getStory`, () => {
it(`should be called, and on success, set this.story`, () => {
spyOn(story.storyService, 'getStory').and.callThrough();
story.ngOnInit();
expect(story.storyService.getStory).toHaveBeenCalled();
expect(story.story.title).toBe('Benefit');
});
});
});
it('should not show the story component if story does not exist', () => {
story.story = null;
storyFixture.detectChanges();
expect(storyEl.children.length).toBe(0);
});
it('should show the story component if story exists', () => {
story.story = mockStory;
storyFixture.detectChanges();
expect(storyEl.children.length).not.toBe(0);
});
describe('story components', () => {
beforeEach(() => {
story.story = mockStory;
storyFixture.detectChanges();
});
describe('album cover', () => {
var element,
img;
beforeEach(() => {
element = storyEl.querySelector('album-cover');
img = element.querySelector('img');
});
it(`should be passed the story albumCover and title to the album cover component`, () => {
expect(img.attributes.src.value).toBe(mockStory.albumCover);
expect(img.attributes.alt.value).toBe(mockStory.title);
});
});
describe('author', () => {
var element,
img,
nameEl;
beforeEach(() => {
element = storyEl.querySelector('author');
img = element.querySelector('img');
nameEl = element.querySelector('.name');
});
it(`should be passed the author name and avatar`, () => {
expect(img.attributes.src.value).toBe(story.story.author.avatar);
expect(img.attributes.alt.value).toBe(story.story.author.name);
expect(nameEl.innerText).toBe(story.story.author.name);
});
});
describe('story duration', () => {
var element;
beforeEach(() => {
element = storyEl.querySelector('.story-duration');
});
it(`should be passed the word count to generate the total read time`, () => {
story.story.wordCount = 234234;
storyFixture.detectChanges();
expect(element.innerText).toBe(`852 min read`);
});
});
});
});
Look at my describe('album cover'.... The way I'm passing this expectation is that I'm finding the <album-cover> element, then finding the <img> tag inside of it, then checking the <img>'s DOM attributes. To me, this expection should be inside of the album-cover.spec.ts - NOT the story.spec.ts.
My question is: is there a way to test if a parent component passed data into a child component without relying on reading dom values?
You can use overrideTemplate to pass a view just for the test.
return tcb
.overrideTemplate(AlbumCover, '<div>{{valueFromParent}}</div>')
.overrideProviders(Story, [