docs(testing): more samples

This commit is contained in:
Ward Bell 2016-06-03 18:15:14 -07:00
parent 05864c2584
commit 7edc32f350
7 changed files with 265 additions and 116 deletions

View File

@ -13,6 +13,8 @@ import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroService } from './hero.service';
import { BAG_DIRECTIVES, BAG_PROVIDERS } from './bag';
@Component({
selector: 'my-app',
template: `
@ -22,12 +24,23 @@ import { HeroService } from './hero.service';
<a [routerLink]="['Heroes']">Heroes</a>
</nav>
<router-outlet></router-outlet>
<hr>
<h1>Bag-a-specs</h1>
<my-if-parent-comp></my-if-parent-comp>
<h3>External Template Comp</h3>
<external-template-comp></external-template-comp>
<h3>Comp With External Template Comp</h3>
<comp-w-ext-comp></comp-w-ext-comp>
`,
/*
*/
styleUrls: ['app/app.component.css'],
directives: [RouterLink, RouterOutlet],
directives: [RouterLink, RouterOutlet, BAG_DIRECTIVES],
providers: [
ROUTER_PROVIDERS,
HeroService
HeroService,
BAG_PROVIDERS
]
})
@RouteConfig([

View File

@ -4,7 +4,7 @@
* Tests that show what goes wrong when the tests are incorrectly written or have a problem
*/
import {
BadTemplateUrl, ButtonComp,
BadTemplateUrlComp, ButtonComp,
ChildChildComp, ChildComp, ChildWithChildComp,
ExternalTemplateComp,
FancyService, MockFancyService,
@ -134,7 +134,7 @@ xdescribe('async & inject testing errors', () => {
it('should fail with an error from a promise',
async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(BadTemplateUrl);
tcb.createAsync(BadTemplateUrlComp);
})));
itPromise.then(

View File

@ -1,9 +1,9 @@
// Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts
/* tslint:disable:no-unused-variable */
import {
BadTemplateUrl, ButtonComp,
BadTemplateUrlComp, ButtonComp,
ChildChildComp, ChildComp, ChildWithChildComp,
ExternalTemplateComp,
CompWithCompWithExternalTemplate, ExternalTemplateComp,
FancyService, MockFancyService,
InputComp,
MyIfComp, MyIfChildComp, MyIfParentComp,
@ -18,14 +18,16 @@ import { By } from '@angular/platform-browser';
import {
beforeEach, beforeEachProviders,
describe, ddescribe, xdescribe,
expect, it, iit, xit,
it, iit, xit,
async, inject,
fakeAsync, tick, withProviders
} from '@angular/core/testing';
// https://github.com/angular/angular/issues/9017
import {expect} from './expect-proper';
import { ComponentFixture, TestComponentBuilder } from '@angular/compiler/testing';
import { provide } from '@angular/core';
import { ViewMetadata } from '@angular/core';
import { Observable } from 'rxjs/Rx';
@ -116,7 +118,7 @@ describe('using the test injector with the inject helper', () => {
describe('setting up Providers with FancyService', () => {
beforeEachProviders(() => [
provide(FancyService, {useValue: new FancyService()})
{provide: FancyService, useValue: new FancyService()}
]);
it('should use FancyService',
@ -183,16 +185,42 @@ describe('using the test injector with the inject helper', () => {
describe('using `withProviders` for per-test provision', () => {
it('should inject test-local FancyService for this test',
// `withProviders`: set up providers at individual test level
withProviders(() => [provide(FancyService, {useValue: {value: 'fake value'}})])
withProviders(() => [{provide: FancyService, useValue: {value: 'fake value'}}])
// now inject and test
.inject([FancyService], (service: FancyService) => {
expect(service.value).toEqual('fake value');
}));
});
describe('can spy on FancyService', () => {
let spy: jasmine.Spy;
let value: string;
beforeEachProviders(() => [
{provide: FancyService, useValue: new FancyService()}
]);
beforeEach(inject([FancyService], (service: FancyService) => {
spy = spyOn(service, 'getValue').and.callFake(() => 'fake value');
value = service.getValue();
}));
it('FancyService.getValue spy should return fake', () => {
expect(value).toBe('fake value');
});
it('FancyService.getValue spy should have been called', () => {
expect(spy.calls.count()).toBe(1, 'should be called once');
});
});
});
describe('test component builder', function() {
beforeEachProviders(() => [ FancyService ]);
it('should instantiate a component with valid DOM',
async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
@ -314,7 +342,7 @@ describe('test component builder', function() {
tcb.overrideProviders(
TestProvidersComp,
[provide(FancyService, {useClass: MockFancyService})]
[{provide: FancyService, useClass: MockFancyService}]
)
.createAsync(TestProvidersComp)
.then(fixture => {
@ -329,7 +357,7 @@ describe('test component builder', function() {
tcb.overrideViewProviders(
TestViewProvidersComp,
[provide(FancyService, {useClass: MockFancyService})]
[{provide: FancyService, useClass: MockFancyService}]
)
.createAsync(TestViewProvidersComp)
.then(fixture => {
@ -339,7 +367,7 @@ describe('test component builder', function() {
});
})));
it('should allow an external templateUrl',
it('should allow an external template',
async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(ExternalTemplateComp)
@ -348,130 +376,201 @@ describe('test component builder', function() {
expect(fixture.nativeElement)
.toHaveText('from external template\n');
});
})), 10000); // Long timeout because this test makes an actual XHR.
})), 10000); // Long timeout because of actual XHR to fetch template.
describe('(lifecycle hooks w/ MyIfParentComp)', () => {
let fixture: ComponentFixture<MyIfParentComp>;
let parent: MyIfParentComp;
let child: MyIfChildComp;
it('should create a component with a component that has an external template',
async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(CompWithCompWithExternalTemplate)
.then(fixture => {
fixture.detectChanges();
let h3 = fixture.debugElement.query(By.css('h3'));
expect(h3).not.toBeNull('should create CompWithExtComp component');
})
.catch((err) => {
// console.error(err);
throw (err);
});
})), 10000); // Long timeout because of actual XHR to fetch template.
/**
* Get the MyIfChildComp from parent; fail w/ good message if cannot.
*/
function getChild() {
let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp
it('should spy on injected component service',
async(inject([TestComponentBuilder, FancyService],
(tcb: TestComponentBuilder, service: FancyService) => {
// The Hard Way: requires detailed knowledge of the parent template
try {
childDe = fixture.debugElement.children[4].children[0];
} catch (err) { /* we'll report the error */ }
let spy = spyOn(service, 'getValue').and.callThrough();
// DebugElement.queryAll: if we wanted all of many instances:
childDe = fixture.debugElement
.queryAll(function (de) { return de.componentInstance instanceof MyIfChildComp; })[0];
tcb
.createAsync(ExternalTemplateComp)
.then(fixture => {
// WE'LL USE THIS APPROACH !
// DebugElement.query: find first instance (if any)
childDe = fixture.debugElement
.query(function (de) { return de.componentInstance instanceof MyIfChildComp; });
// let spy = spyOn(service, 'getValue').and.callThrough();
if (childDe && childDe.componentInstance) {
child = childDe.componentInstance;
} else {
fail('Unable to find MyIfChildComp within MyIfParentComp');
}
fixture.detectChanges();
expect(spy.calls.count()).toBe(1, 'should be called once');
});
})), 10000); // Long timeout because of actual XHR to fetch template.
return child;
describe('(lifecycle hooks w/ MyIfParentComp)', () => {
let fixture: ComponentFixture<MyIfParentComp>;
let parent: MyIfParentComp;
let child: MyIfChildComp;
/**
* Get the MyIfChildComp from parent; fail w/ good message if cannot.
*/
function getChild() {
let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp
// The Hard Way: requires detailed knowledge of the parent template
try {
childDe = fixture.debugElement.children[4].children[0];
} catch (err) { /* we'll report the error */ }
// DebugElement.queryAll: if we wanted all of many instances:
childDe = fixture.debugElement
.queryAll(function (de) { return de.componentInstance instanceof MyIfChildComp; })[0];
// WE'LL USE THIS APPROACH !
// DebugElement.query: find first instance (if any)
childDe = fixture.debugElement
.query(function (de) { return de.componentInstance instanceof MyIfChildComp; });
if (childDe && childDe.componentInstance) {
child = childDe.componentInstance;
} else {
fail('Unable to find MyIfChildComp within MyIfParentComp');
}
// Create MyIfParentComp TCB and component instance before each test (async beforeEach)
beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(MyIfParentComp)
.then(fix => {
fixture = fix;
parent = fixture.debugElement.componentInstance;
});
})));
return child;
}
it('should instantiate parent component', () => {
expect(parent).not.toBeNull('parent component should exist');
});
// Create MyIfParentComp TCB and component instance before each test (async beforeEach)
beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(MyIfParentComp)
.then(fix => {
fixture = fix;
parent = fixture.debugElement.componentInstance;
});
})));
it('parent component OnInit should NOT be called before first detectChanges()', () => {
expect(parent.ngOnInitCalled).toEqual(false);
});
it('should instantiate parent component', () => {
expect(parent).not.toBeNull('parent component should exist');
});
it('parent component OnInit should be called after first detectChanges()', () => {
fixture.detectChanges();
expect(parent.ngOnInitCalled).toEqual(true);
});
it('parent component OnInit should NOT be called before first detectChanges()', () => {
expect(parent.ngOnInitCalled).toEqual(false);
});
it('child component should exist after OnInit', () => {
fixture.detectChanges();
getChild();
expect(child instanceof MyIfChildComp).toEqual(true, 'should create child');
});
it('parent component OnInit should be called after first detectChanges()', () => {
fixture.detectChanges();
expect(parent.ngOnInitCalled).toEqual(true);
});
it('should have called child component\'s OnInit ', () => {
fixture.detectChanges();
getChild();
expect(child.ngOnInitCalled).toEqual(true);
});
it('child component should exist after OnInit', () => {
fixture.detectChanges();
getChild();
expect(child instanceof MyIfChildComp).toEqual(true, 'should create child');
});
it('child component called OnChanges once', () => {
fixture.detectChanges();
getChild();
expect(child.ngOnChangesCounter).toEqual(1);
});
it('should have called child component\'s OnInit ', () => {
fixture.detectChanges();
getChild();
expect(child.ngOnInitCalled).toEqual(true);
});
it('changed parent value flows to child', () => {
fixture.detectChanges();
getChild();
it('child component called OnChanges once', () => {
fixture.detectChanges();
getChild();
expect(child.ngOnChangesCounter).toEqual(1);
});
parent.parentValue = 'foo';
it('changed parent value flows to child', () => {
fixture.detectChanges();
getChild();
parent.parentValue = 'foo';
fixture.detectChanges();
expect(child.ngOnChangesCounter).toEqual(2,
'expected 2 changes: initial value and changed value');
expect(child.childValue).toEqual('foo',
'childValue should eq changed parent value');
});
it('changed child value flows to parent', async(() => {
fixture.detectChanges();
getChild();
child.childValue = 'bar';
return new Promise(resolve => {
// Wait one JS engine turn!
setTimeout(() => resolve(), 0);
}).then(() => {
fixture.detectChanges();
expect(child.ngOnChangesCounter).toEqual(2,
'expected 2 changes: initial value and changed value');
expect(child.childValue).toEqual('foo',
'childValue should eq changed parent value');
expect(parent.parentValue).toEqual('bar',
'parentValue should eq changed parent value');
});
it('changed child value flows to parent', async(() => {
fixture.detectChanges();
getChild();
}));
child.childValue = 'bar';
it('clicking "Close Child" triggers child OnDestroy', () => {
fixture.detectChanges();
getChild();
return new Promise(resolve => {
// Wait one JS engine turn!
setTimeout(() => resolve(), 0);
}).then(() => {
fixture.detectChanges();
expect(child.ngOnChangesCounter).toEqual(2,
'expected 2 changes: initial value and changed value');
expect(parent.parentValue).toEqual('bar',
'parentValue should eq changed parent value');
});
}));
it('clicking "Close Child" triggers child OnDestroy', () => {
fixture.detectChanges();
getChild();
let btn = fixture.debugElement.query(By.css('button'));
btn.triggerEventHandler('click', null);
fixture.detectChanges();
expect(child.ngOnDestroyCalled).toEqual(true);
});
let btn = fixture.debugElement.query(By.css('button'));
btn.triggerEventHandler('click', null);
fixture.detectChanges();
expect(child.ngOnDestroyCalled).toEqual(true);
});
});
});
describe('test component builder in beforeEach (comp w/ external template)', function() {
let fixture: ComponentFixture<ExternalTemplateComp>;
beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(ExternalTemplateComp).then(fix => fixture = fix);
}))
);
// May need Long timeout because this test makes an actual XHR to get template
// , 10000); WHY CAN'T I ADD TIMEOUT OVERRIDE
it('should allow an external template', () => {
fixture.detectChanges();
expect(fixture.nativeElement)
.toHaveText('from external template\n');
});
});
describe('test component builder in beforeEach (comp w/ internal comp w/ external template)', function() {
let fixture: ComponentFixture<CompWithCompWithExternalTemplate>;
beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(CompWithCompWithExternalTemplate).then(fix => fixture = fix)
.catch((err) => {
// console.error(err);
throw (err);
});
}))
);
// May need Long timeout because this test makes an actual XHR to get template
// , 10000); WHY CAN'T I ADD TIMEOUT OVERRIDE
it('should allow an external template', () => {
fixture.detectChanges();
let h3 = fixture.debugElement.query(By.css('h3'));
expect(h3).not.toBeNull('should create CompWithExtComp component');
});
});
//////// Testing Framework Bugs? /////
import { HeroService } from './hero.service';
@ -495,7 +594,7 @@ describe('tcb.overrideProviders', () => {
tcb.overrideProviders(
AnotherProvidersComp,
[provide(HeroService, {useValue: {}})]
[{provide: HeroService, useValue: {}}]
)
.createAsync(AnotherProvidersComp);
})));

View File

@ -1,6 +1,6 @@
// Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts
/* tslint:disable:forin */
import { Component, EventEmitter, Injectable, Input, Output,
import { Component, EventEmitter, Injectable, Input, Output, Optional,
OnInit, OnChanges, OnDestroy, SimpleChange } from '@angular/core';
import { Observable } from 'rxjs/Rx';
@ -13,6 +13,8 @@ import { Observable } from 'rxjs/Rx';
export class FancyService {
value: string = 'real value';
getValue() { return this.value; }
getAsyncValue() { return Promise.resolve('async value'); }
getObservableValue() { return Observable.of('observable value'); }
@ -123,20 +125,36 @@ export class TestViewProvidersComp {
constructor(private fancyService: FancyService) {}
}
@Component({
moduleId: module.id,
selector: 'external-template-comp',
templateUrl: 'bag-external-template.html'
})
export class ExternalTemplateComp { }
export class ExternalTemplateComp {
serviceValue: string;
constructor(@Optional() private service: FancyService) { }
ngOnInit() {
if (this.service) { this.serviceValue = this.service.getValue(); }
}
}
@Component({
selector: 'comp-w-ext-comp',
template: `
<h3>comp-w-ext-comp</h3>
<external-template-comp></external-template-comp>
`,
directives: [ExternalTemplateComp]
})
export class CompWithCompWithExternalTemplate { }
@Component({
selector: 'bad-template-comp',
templateUrl: 'non-existant.html'
})
export class BadTemplateUrl { }
export class BadTemplateUrlComp { }
///////// MyIfChildComp ////////
@ -222,3 +240,16 @@ export class MyIfParentComp implements OnInit {
this.toggleLabel = this.showChild ? 'Close' : 'Show';
}
}
export const BAG_PROVIDERS = [FancyService];
export const BAG_DIRECTIVES = [
ButtonComp,
ChildChildComp, ChildComp, ChildWithChildComp,
ExternalTemplateComp, CompWithCompWithExternalTemplate,
InputComp,
MyIfComp, MyIfChildComp, MyIfParentComp,
MockChildComp, MockChildChildComp,
ParentComp,
TestProvidersComp, TestViewProvidersComp
];

View File

@ -0,0 +1,9 @@
// See https://github.com/angular/angular/issues/9017
import { expect as expectCore} from '@angular/core/testing';
import { NgMatchers } from '@angular/platform-browser/testing';
export function expect(spy: Function): NgMatchers;
export function expect(actual: any): NgMatchers;
export function expect(actual: any): NgMatchers {
return expectCore(actual) as NgMatchers;
}

View File

@ -1,6 +1,5 @@
import { bootstrap } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app.component';
import { MyIfParentComp } from './bag';
bootstrap(AppComponent);
bootstrap(MyIfParentComp);

View File

@ -22,7 +22,5 @@
<body>
<my-app>Loading...</my-app>
<hr>
<my-if-parent-comp>Loading MyIfParentComp ...</my-if-parent-comp>
</body>
</html>