docs(testing) adapt sample code to beta.16 breaking changes

This commit is contained in:
Ward Bell 2016-04-26 16:15:40 -07:00
parent 097505b4e8
commit 4bbaac127d
8 changed files with 381 additions and 317 deletions

View File

@ -20,24 +20,30 @@ module.exports = function(config) {
flags: ['--no-sandbox'] flags: ['--no-sandbox']
} }
}, },
files: [ files: [
// Angular and shim libraries loaded by Karma // System.js for module loading
{ pattern: 'node_modules/systemjs/dist/system-polyfills.js', included: true, watched: true }, 'node_modules/systemjs/dist/system-polyfills.js',
{ pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: true }, 'node_modules/systemjs/dist/system.src.js',
{ pattern: 'node_modules/es6-shim/es6-shim.js', included: true, watched: true },
{ pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', included: true, watched: true },
{ pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: true },
{ pattern: 'node_modules/angular2/bundles/angular2.js', included: true, watched: true },
{ pattern: 'node_modules/angular2/bundles/testing.dev.js', included: true, watched: true },
// External libraries loaded by Karma // Polyfills
{ pattern: 'node_modules/angular2/bundles/http.dev.js', included: true, watched: true }, 'node_modules/es6-shim/es6-shim.js',
{ pattern: 'node_modules/angular2/bundles/router.dev.js', included: true, watched: true }, 'node_modules/angular2/bundles/angular2-polyfills.js',
{ pattern: 'node_modules/a2-in-memory-web-api/web-api.js', included: true, watched: true },
// Configures module loader w/ app and specs, then launch karma // Zone.js dependencies
{ pattern: 'karma-test-shim.js', included: true, watched: true }, // Note - do not include zone.js itself or long-stack-trace-zone.js` here as
// they are included already in angular2-polyfills
'node_modules/zone.js/dist/jasmine-patch.js',
'node_modules/zone.js/dist/async-test.js',
'node_modules/zone.js/dist/fake-async-test.js',
// RxJs
'node_modules/rxjs/bundles/Rx.js',
// Angular 2 itself and the testing library
'node_modules/angular2/bundles/angular2.js',
'node_modules/angular2/bundles/testing.dev.js',
'karma-test-shim.js',
// transpiled application & spec code paths loaded via module imports // transpiled application & spec code paths loaded via module imports
{pattern: appBase + '**/*.js', included: false, watched: true}, {pattern: appBase + '**/*.js', included: false, watched: true},

View File

@ -2,14 +2,13 @@
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { By } from 'angular2/platform/browser'; import { By } from 'angular2/platform/browser';
import { provide } from 'angular2/core'; import { DebugElement, provide } from 'angular2/core';
import { import {
beforeEach, beforeEachProviders, beforeEach, beforeEachProviders,
describe, ddescribe, xdescribe, describe, ddescribe, xdescribe,
expect, it, iit, xit, expect, it, iit, xit,
inject, injectAsync, async, inject, ComponentFixture, TestComponentBuilder
ComponentFixture, TestComponentBuilder
} from 'angular2/testing'; } from 'angular2/testing';
import { Hero, HeroService, MockHeroService } from './mock-hero.service'; import { Hero, HeroService, MockHeroService } from './mock-hero.service';
@ -22,8 +21,8 @@ describe('AppComponent', () => {
let fixture: ComponentFixture; let fixture: ComponentFixture;
let comp: AppComponent; let comp: AppComponent;
beforeEach(injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb tcb
.overrideDirective(AppComponent, RouterLink, MockRouterLink) .overrideDirective(AppComponent, RouterLink, MockRouterLink)
.overrideDirective(AppComponent, RouterOutlet, MockRouterOutlet) .overrideDirective(AppComponent, RouterOutlet, MockRouterOutlet)
.overrideProviders(AppComponent, [ .overrideProviders(AppComponent, [
@ -35,7 +34,7 @@ describe('AppComponent', () => {
fixture = fix; fixture = fix;
comp = fixture.debugElement.componentInstance; comp = fixture.debugElement.componentInstance;
}); });
})); })));
it('can instantiate it', () => { it('can instantiate it', () => {
expect(comp).not.toBeNull(); expect(comp).not.toBeNull();
@ -51,8 +50,8 @@ describe('AppComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
let links = fixture.debugElement let links = fixture.debugElement
.queryAll(function (de) { return de.componentInstance instanceof MockRouterLink; }) .queryAll(By.directive(MockRouterLink))
.map(de => <MockRouterLink> de.componentInstance); .map(de => <MockRouterLink> extractDirective(de, MockRouterLink));
expect(links.length).toEqual(2, 'should have 2 links'); expect(links.length).toEqual(2, 'should have 2 links');
expect(links[0].routeParams[0]).toEqual('Dashboard', '1st link should go to Dashboard'); expect(links[0].routeParams[0]).toEqual('Dashboard', '1st link should go to Dashboard');
@ -67,11 +66,12 @@ describe('AppComponent', () => {
// Heroes RouterLink DebugElement // Heroes RouterLink DebugElement
let heroesDe = fixture.debugElement let heroesDe = fixture.debugElement
.queryAll(function (de) { return de.componentInstance instanceof MockRouterLink; })[1]; .queryAll(By.directive(MockRouterLink))[1];
expect(heroesDe).not.toBeNull('should 2nd link'); expect(heroesDe).toBeDefined('should have a 2nd RouterLink');
let link = <MockRouterLink> extractDirective(heroesDe, MockRouterLink);
let link = <MockRouterLink> heroesDe.componentInstance;
expect(link.navigatedTo).toBeNull('link should not have navigate yet'); expect(link.navigatedTo).toBeNull('link should not have navigate yet');
heroesDe.triggerEventHandler('click', null); heroesDe.triggerEventHandler('click', null);
@ -82,3 +82,15 @@ describe('AppComponent', () => {
}); });
}); });
///////////// Helpers ////////////////////
import { Type } from 'angular2/src/facade/lang';
/**
* Get the directive instance from the DebugElement to which it is attached
*/
function extractDirective(de: DebugElement, directive: Type): any {
return de.injector.get(
de.providerTokens[de.providerTokens.indexOf(directive)]
);
}

View File

@ -0,0 +1,172 @@
// Based on https://github.com/angular/angular/blob/master/modules/angular2/test/testing/testing_public_spec.ts
/* tslint:disable:no-unused-variable */
/**
* Tests that show what goes wrong when the tests are incorrectly written or have a problem
*/
import {
BadTemplateUrl, ButtonComp,
ChildChildComp, ChildComp, ChildWithChildComp,
ExternalTemplateComp,
FancyService, MockFancyService,
InputComp,
MyIfComp, MyIfChildComp, MyIfParentComp,
MockChildComp, MockChildChildComp,
ParentComp,
TestProvidersComp, TestViewProvidersComp
} from './bag';
import { DebugElement } from 'angular2/core';
import { By } from 'angular2/platform/browser';
import {
beforeEach, beforeEachProviders, withProviders,
describe, ddescribe, xdescribe,
expect, it, iit, xit,
async, inject, fakeAsync, tick,
ComponentFixture, TestComponentBuilder
} from 'angular2/testing';
import { provide } from 'angular2/core';
import { ViewMetadata } from 'angular2/core';
import { PromiseWrapper } from 'angular2/src/facade/promise';
import { XHR } from 'angular2/src/compiler/xhr';
import { XHRImpl } from 'angular2/src/platform/browser/xhr_impl';
import { Observable } from 'rxjs/Rx';
//////// SPECS /////////////
xdescribe('async & inject testing errors', () => {
let originalJasmineIt: any;
let originalJasmineBeforeEach: any;
let patchJasmineIt = () => {
let deferred = PromiseWrapper.completer();
originalJasmineIt = jasmine.getEnv().it;
jasmine.getEnv().it = (description: string, fn: Function): jasmine.Spec => {
let done = () => { deferred.resolve(); };
(<any>done).fail = (err: any) => { deferred.reject(err); };
fn(done);
return null;
};
return deferred.promise;
};
let restoreJasmineIt = () => { jasmine.getEnv().it = originalJasmineIt; };
let patchJasmineBeforeEach = () => {
let deferred = PromiseWrapper.completer();
originalJasmineBeforeEach = jasmine.getEnv().beforeEach;
jasmine.getEnv().beforeEach = (fn: any): void => {
let done = () => { deferred.resolve(); };
(<any>done).fail = (err: any) => { deferred.reject(err); };
fn(done);
return null;
};
return deferred.promise;
};
let restoreJasmineBeforeEach =
() => { jasmine.getEnv().beforeEach = originalJasmineBeforeEach; };
const shouldNotSucceed =
(done: DoneFn) => () => done.fail( 'Expected an error, but did not get one.');
const shouldFail =
(done: DoneFn, emsg: string) => (err: any) => { expect(err).toEqual(emsg); done(); };
it('should fail when an asynchronous error is thrown', (done: DoneFn) => {
let itPromise = patchJasmineIt();
it('throws an async error',
async(inject([], () => { setTimeout(() => { throw new Error('bar'); }, 0); })));
itPromise.then(
shouldNotSucceed(done),
err => {
expect(err).toEqual('bar');
done();
});
restoreJasmineIt();
});
it('should fail when a returned promise is rejected', (done: DoneFn) => {
let itPromise = patchJasmineIt();
it('should fail with an error from a promise', async(inject([], () => {
let deferred = PromiseWrapper.completer();
let p = deferred.promise.then(() => { expect(1).toEqual(2); });
deferred.reject('baz');
return p;
})));
itPromise.then(
shouldNotSucceed(done),
err => {
expect(err).toEqual('Uncaught (in promise): baz');
done();
});
restoreJasmineIt();
});
it('should fail when an error occurs inside inject', (done: DoneFn) => {
let itPromise = patchJasmineIt();
it('throws an error', inject([], () => { throw new Error('foo'); }));
itPromise.then(
shouldNotSucceed(done),
shouldFail(done, 'foo')
);
restoreJasmineIt();
});
// TODO(juliemr): reenable this test when we are using a test zone and can capture this error.
it('should fail when an asynchronous error is thrown', (done: DoneFn) => {
let itPromise = patchJasmineIt();
it('throws an async error',
async(inject([], () => { setTimeout(() => { throw new Error('bar'); }, 0); })));
itPromise.then(
shouldNotSucceed(done),
shouldFail(done, 'bar')
);
restoreJasmineIt();
});
it('should fail when XHR loading of a template fails', (done: DoneFn) => {
let itPromise = patchJasmineIt();
it('should fail with an error from a promise',
async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
tcb.createAsync(BadTemplateUrl);
})));
itPromise.then(
shouldNotSucceed(done),
shouldFail(done, 'Uncaught (in promise): Failed to load non-existant.html')
);
restoreJasmineIt();
}, 10000);
describe('using beforeEachProviders', () => {
beforeEachProviders(() => [provide(FancyService, {useValue: new FancyService()})]);
beforeEach(
inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('real value'); }));
describe('nested beforeEachProviders', () => {
it('should fail when the injector has already been used', () => {
patchJasmineBeforeEach();
expect(() => {
beforeEachProviders(() => [provide(FancyService, {useValue: new FancyService()})]);
})
.toThrowError('beforeEachProviders was called after the injector had been used ' +
'in a beforeEach or it block. This invalidates the test injector');
restoreJasmineBeforeEach();
});
});
});
});

View File

@ -19,7 +19,7 @@ import {
beforeEach, beforeEachProviders, withProviders, beforeEach, beforeEachProviders, withProviders,
describe, ddescribe, xdescribe, describe, ddescribe, xdescribe,
expect, it, iit, xit, expect, it, iit, xit,
inject, injectAsync, fakeAsync, tick, async, inject, fakeAsync, tick,
ComponentFixture, TestComponentBuilder ComponentFixture, TestComponentBuilder
} from 'angular2/testing'; } from 'angular2/testing';
@ -28,12 +28,7 @@ import { ViewMetadata } from 'angular2/core';
import { PromiseWrapper } from 'angular2/src/facade/promise'; import { PromiseWrapper } from 'angular2/src/facade/promise';
import { XHR } from 'angular2/src/compiler/xhr'; import { XHR } from 'angular2/src/compiler/xhr';
import { XHRImpl } from 'angular2/src/platform/browser/xhr_impl'; import { XHRImpl } from 'angular2/src/platform/browser/xhr_impl';
import { Observable } from 'rxjs/Rx';
/////////// Module Preparation ///////////////////////
interface Done {
(): void;
fail: (err: any) => void;
}
//////// SPECS ///////////// //////// SPECS /////////////
@ -78,16 +73,47 @@ describe('angular2 jasmine matchers', () => {
}); });
}); });
describe('using the test injector with the inject helper', () => { describe('using the async helper', () => {
it('should run normal tests', () => { expect(true).toEqual(true); }); let actuallyDone = false;
it('should run normal async tests', (done: Done) => { beforeEach(() => { actuallyDone = false; });
afterEach(() => { expect(actuallyDone).toEqual(true); });
it('should run normal test', () => { actuallyDone = true; });
it('should run normal async test', (done: DoneFn) => {
setTimeout(() => { setTimeout(() => {
expect(true).toEqual(true); actuallyDone = true;
done(); done();
}, 0); }, 0);
}); });
it('should run async test with task',
async(() => { setTimeout(() => { actuallyDone = true; }, 0); }));
it('should run async test with successful promise', async(() => {
let p = new Promise(resolve => { setTimeout(resolve, 10); });
p.then(() => { actuallyDone = true; });
}));
it('should run async test with failed promise', async(() => {
let p = new Promise((resolve, reject) => { setTimeout(reject, 10); });
p.catch(() => { actuallyDone = true; });
}));
it('should run async test with successful Observable', async(() => {
let source = Observable.of(true).delay(10);
source.subscribe(
val => {},
err => fail(err),
() => { actuallyDone = true; } // completed
);
}));
});
describe('using the test injector with the inject helper', () => {
it('provides a real XHR instance', it('provides a real XHR instance',
inject([XHR], (xhr: any) => { expect(xhr).toBeAnInstanceOf(XHRImpl); })); inject([XHR], (xhr: any) => { expect(xhr).toBeAnInstanceOf(XHRImpl); }));
@ -102,14 +128,33 @@ describe('using the test injector with the inject helper', () => {
})); }));
it('test should wait for FancyService.getAsyncValue', it('test should wait for FancyService.getAsyncValue',
injectAsync([FancyService], (service: FancyService) => { async(inject([FancyService], (service: FancyService) => {
return service.getAsyncValue().then( service.getAsyncValue().then(
(value) => { expect(value).toEqual('async value'); }); value => { expect(value).toEqual('async value'); });
})); })));
it('test should wait for FancyService.getTimeoutValue',
async(inject([FancyService], (service: FancyService) => {
service.getTimeoutValue().then(
value => { expect(value).toEqual('timeout value'); });
})));
it('test should wait for FancyService.getObservableValue',
async(inject([FancyService], (service: FancyService) => {
service.getObservableValue().subscribe(
value => { expect(value).toEqual('observable value'); }
);
})));
it('test should wait for FancyService.getObservableDelayValue',
async(inject([FancyService], (service: FancyService) => {
service.getObservableDelayValue().subscribe(
value => { expect(value).toEqual('observable delay value'); }
);
})));
// Experimental: write async tests synchonously by faking async processing
it('should allow the use of fakeAsync (Experimental)', it('should allow the use of fakeAsync (Experimental)',
inject([FancyService], fakeAsync((service: FancyService) => { fakeAsync(inject([FancyService], (service: FancyService) => {
let value: any; let value: any;
service.getAsyncValue().then((val: any) => value = val); service.getAsyncValue().then((val: any) => value = val);
tick(); // Trigger JS engine cycle until all promises resolve. tick(); // Trigger JS engine cycle until all promises resolve.
@ -128,9 +173,9 @@ describe('using the test injector with the inject helper', () => {
}); });
describe('using async within beforeEach', () => { describe('using async within beforeEach', () => {
beforeEach(injectAsync([FancyService], (service: FancyService) => { beforeEach(async(inject([FancyService], (service: FancyService) => {
return service.getAsyncValue().then(value => { service.value = value; }); service.getAsyncValue().then(value => { service.value = value; });
})); })));
it('should use asynchronously modified value ... in synchronous test', it('should use asynchronously modified value ... in synchronous test',
inject([FancyService], (service: FancyService) => { inject([FancyService], (service: FancyService) => {
@ -152,18 +197,18 @@ describe('using the test injector with the inject helper', () => {
describe('test component builder', function() { describe('test component builder', function() {
it('should instantiate a component with valid DOM', it('should instantiate a component with valid DOM',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(ChildComp).then(fixture => { tcb.createAsync(ChildComp).then(fixture => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Original Child'); expect(fixture.nativeElement).toHaveText('Original Child');
}); });
})); })));
it('should allow changing members of the component', it('should allow changing members of the component',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(MyIfComp).then(fixture => { tcb.createAsync(MyIfComp).then(fixture => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('MyIf()'); expect(fixture.nativeElement).toHaveText('MyIf()');
@ -171,12 +216,12 @@ describe('test component builder', function() {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('MyIf(More)'); expect(fixture.nativeElement).toHaveText('MyIf(More)');
}); });
})); })));
it('should support clicking a button', it('should support clicking a button',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(ButtonComp).then(fixture => { tcb.createAsync(ButtonComp).then(fixture => {
let comp = <ButtonComp> fixture.componentInstance; let comp = <ButtonComp> fixture.componentInstance;
expect(comp.wasClicked).toEqual(false, 'wasClicked should be false at start'); expect(comp.wasClicked).toEqual(false, 'wasClicked should be false at start');
@ -188,14 +233,14 @@ describe('test component builder', function() {
// btn.nativeElement.click(); // this often works too ... but not all the time! // btn.nativeElement.click(); // this often works too ... but not all the time!
expect(comp.wasClicked).toEqual(true, 'wasClicked should be true after click'); expect(comp.wasClicked).toEqual(true, 'wasClicked should be true after click');
}); });
})); })));
it('should support entering text in input box (ngModel)', it('should support entering text in input box (ngModel)',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
let origName = 'John'; let origName = 'John';
let newName = 'Sally'; let newName = 'Sally';
return tcb.createAsync(InputComp).then(fixture => { tcb.createAsync(InputComp).then(fixture => {
let comp = <InputComp> fixture.componentInstance; let comp = <InputComp> fixture.componentInstance;
expect(comp.name).toEqual(origName, `At start name should be ${origName} `); expect(comp.name).toEqual(origName, `At start name should be ${origName} `);
@ -212,23 +257,23 @@ describe('test component builder', function() {
expect(inputBox.value).toEqual(newName, expect(inputBox.value).toEqual(newName,
`After value change and detectChanges, name should now be ${newName} `); `After value change and detectChanges, name should now be ${newName} `);
}); });
})); })));
it('should override a template', it('should override a template',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideTemplate(MockChildComp, '<span>Mock</span>') tcb.overrideTemplate(MockChildComp, '<span>Mock</span>')
.createAsync(MockChildComp) .createAsync(MockChildComp)
.then(fixture => { .then(fixture => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Mock'); expect(fixture.nativeElement).toHaveText('Mock');
}); });
})); })));
it('should override a view', it('should override a view',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideView( tcb.overrideView(
ChildComp, ChildComp,
new ViewMetadata({template: '<span>Modified {{childBinding}}</span>'}) new ViewMetadata({template: '<span>Modified {{childBinding}}</span>'})
) )
@ -238,25 +283,25 @@ describe('test component builder', function() {
expect(fixture.nativeElement).toHaveText('Modified Child'); expect(fixture.nativeElement).toHaveText('Modified Child');
}); });
})); })));
it('should override component directives', it('should override component directives',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideDirective(ParentComp, ChildComp, MockChildComp) tcb.overrideDirective(ParentComp, ChildComp, MockChildComp)
.createAsync(ParentComp) .createAsync(ParentComp)
.then(fixture => { .then(fixture => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Parent(Mock)'); expect(fixture.nativeElement).toHaveText('Parent(Mock)');
}); });
})); })));
it('should override child component\'s directives', it('should override child component\'s directives',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp) tcb.overrideDirective(ParentComp, ChildComp, ChildWithChildComp)
.overrideDirective(ChildWithChildComp, ChildChildComp, MockChildChildComp) .overrideDirective(ChildWithChildComp, ChildChildComp, MockChildChildComp)
.createAsync(ParentComp) .createAsync(ParentComp)
.then(fixture => { .then(fixture => {
@ -265,12 +310,12 @@ describe('test component builder', function() {
.toHaveText('Parent(Original Child(ChildChild Mock))'); .toHaveText('Parent(Original Child(ChildChild Mock))');
}); });
})); })));
it('should override a provider', it('should override a provider',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideProviders( tcb.overrideProviders(
TestProvidersComp, TestProvidersComp,
[provide(FancyService, {useClass: MockFancyService})] [provide(FancyService, {useClass: MockFancyService})]
) )
@ -280,12 +325,12 @@ describe('test component builder', function() {
expect(fixture.nativeElement) expect(fixture.nativeElement)
.toHaveText('injected value: mocked out value'); .toHaveText('injected value: mocked out value');
}); });
})); })));
it('should override a viewProvider', it('should override a viewProvider',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideViewProviders( tcb.overrideViewProviders(
TestViewProvidersComp, TestViewProvidersComp,
[provide(FancyService, {useClass: MockFancyService})] [provide(FancyService, {useClass: MockFancyService})]
) )
@ -295,18 +340,18 @@ describe('test component builder', function() {
expect(fixture.nativeElement) expect(fixture.nativeElement)
.toHaveText('injected value: mocked out value'); .toHaveText('injected value: mocked out value');
}); });
})); })));
it('should allow an external templateUrl', it('should allow an external templateUrl',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(ExternalTemplateComp) tcb.createAsync(ExternalTemplateComp)
.then(fixture => { .then(fixture => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.nativeElement) expect(fixture.nativeElement)
.toHaveText('from external template\n'); .toHaveText('from external template\n');
}); });
}), 10000); // Long timeout because this test makes an actual XHR. })), 10000); // Long timeout because this test makes an actual XHR.
describe('(lifecycle hooks w/ MyIfParentComp)', () => { describe('(lifecycle hooks w/ MyIfParentComp)', () => {
let fixture: ComponentFixture; let fixture: ComponentFixture;
@ -344,13 +389,13 @@ describe('test component builder', function() {
} }
// Create MyIfParentComp TCB and component instance before each test (async beforeEach) // Create MyIfParentComp TCB and component instance before each test (async beforeEach)
beforeEach(injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { beforeEach(async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(MyIfParentComp) tcb.createAsync(MyIfParentComp)
.then(fix => { .then(fix => {
fixture = fix; fixture = fix;
parent = fixture.debugElement.componentInstance; parent = fixture.debugElement.componentInstance;
}); });
})); })));
it('should instantiate parent component', () => { it('should instantiate parent component', () => {
expect(parent).not.toBeNull('parent component should exist'); expect(parent).not.toBeNull('parent component should exist');
@ -396,7 +441,7 @@ describe('test component builder', function() {
'childValue should eq changed parent value'); 'childValue should eq changed parent value');
}); });
it('changed child value flows to parent', injectAsync([], () => { it('changed child value flows to parent', async(() => {
fixture.detectChanges(); fixture.detectChanges();
getChild(); getChild();
@ -419,24 +464,6 @@ describe('test component builder', function() {
return p; return p;
})); }));
/* Will soon be able to write it like this:
it('changed child value flows to parent', async(() => {
fixture.detectChanges();
getChild();
child.childValue = 'bar';
// Wait one JS engine turn!
setTimeout(() => {
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');
}, 0);
}));
*/
it('clicking "Close Child" triggers child OnDestroy', () => { it('clicking "Close Child" triggers child OnDestroy', () => {
fixture.detectChanges(); fixture.detectChanges();
getChild(); getChild();
@ -451,173 +478,6 @@ describe('test component builder', function() {
}); });
}); });
describe('inject/async testing errors', () => {
let originalJasmineIt: any;
let originalJasmineBeforeEach: any;
let patchJasmineIt = () => {
let deferred = PromiseWrapper.completer();
originalJasmineIt = jasmine.getEnv().it;
jasmine.getEnv().it = (description: string, fn: Function): jasmine.Spec => {
let done = () => { deferred.resolve(); };
(<any>done).fail = (err: any) => { deferred.reject(err); };
fn(done);
return null;
};
return deferred.promise;
};
let restoreJasmineIt = () => { jasmine.getEnv().it = originalJasmineIt; };
let patchJasmineBeforeEach = () => {
let deferred = PromiseWrapper.completer();
originalJasmineBeforeEach = jasmine.getEnv().beforeEach;
jasmine.getEnv().beforeEach = (fn: any): void => {
let done = () => { deferred.resolve(); };
(<any>done).fail = (err: any) => { deferred.reject(err); };
fn(done);
return null;
};
return deferred.promise;
};
let restoreJasmineBeforeEach =
() => { jasmine.getEnv().beforeEach = originalJasmineBeforeEach; };
const shouldNotSucceed =
(done: Done) => () => done.fail( 'Expected function to throw, but it did not');
const shouldFail =
(done: Done, emsg: string) => (err: any) => { expect(err).toEqual(emsg); done(); };
it('injectAsync should fail when return was forgotten in it', (done: Done) => {
let itPromise = patchJasmineIt();
it('forgets to return a proimse', injectAsync([], () => { return true; }));
itPromise.then(
shouldNotSucceed(done),
shouldFail(done,
'Error: injectAsync was expected to return a promise, but the returned value was: true')
);
restoreJasmineIt();
});
it('inject should fail if a value was returned', (done: Done) => {
let itPromise = patchJasmineIt();
it('returns a value', inject([], () => { return true; }));
itPromise.then(
shouldNotSucceed(done),
shouldFail(done,
'Error: inject returned a value. Did you mean to use injectAsync? Returned value was: true')
);
restoreJasmineIt();
});
it('injectAsync should fail when return was forgotten in beforeEach', (done: Done) => {
let beforeEachPromise = patchJasmineBeforeEach();
beforeEach(injectAsync([], () => { return true; }));
beforeEachPromise.then(
shouldNotSucceed(done),
shouldFail(done,
'Error: injectAsync was expected to return a promise, but the returned value was: true')
);
restoreJasmineBeforeEach();
});
it('inject should fail if a value was returned in beforeEach', (done: Done) => {
let beforeEachPromise = patchJasmineBeforeEach();
beforeEach(inject([], () => { return true; }));
beforeEachPromise.then(
shouldNotSucceed(done),
shouldFail(done,
'Error: inject returned a value. Did you mean to use injectAsync? Returned value was: true')
);
restoreJasmineBeforeEach();
});
it('should fail when an error occurs inside inject', (done: Done) => {
let itPromise = patchJasmineIt();
it('throws an error', inject([], () => { throw new Error('foo'); }));
itPromise.then(
shouldNotSucceed(done),
err => { expect(err.message).toEqual('foo'); done(); }
);
restoreJasmineIt();
});
// TODO(juliemr): reenable this test when we are using a test zone and can capture this error.
xit('should fail when an asynchronous error is thrown', (done: Done) => {
let itPromise = patchJasmineIt();
it('throws an async error',
injectAsync([], () => { setTimeout(() => { throw new Error('bar'); }, 0); }));
itPromise.then(
shouldNotSucceed(done),
err => { expect(err.message).toEqual('bar'); done(); }
);
restoreJasmineIt();
});
it('should fail when a returned promise is rejected', (done: Done) => {
let itPromise = patchJasmineIt();
it('should fail with an error from a promise', injectAsync([], () => {
let deferred = PromiseWrapper.completer();
let p = deferred.promise.then(() => { expect(1).toEqual(2); });
deferred.reject('baz');
return p;
}));
itPromise.then(
shouldNotSucceed(done),
shouldFail(done, 'baz')
);
restoreJasmineIt();
});
it('should fail when an XHR fails', (done: Done) => {
let itPromise = patchJasmineIt();
it('should fail with an error from a promise',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(BadTemplateUrl);
}));
itPromise.then(
shouldNotSucceed(done),
shouldFail(done, 'Failed to load non-existant.html')
);
restoreJasmineIt();
}, 10000);
describe('using beforeEachProviders', () => {
beforeEachProviders(() => [provide(FancyService, {useValue: new FancyService()})]);
beforeEach(
inject([FancyService], (service: FancyService) => { expect(service.value).toEqual('real value'); }));
describe('nested beforeEachProviders', () => {
it('should fail when the injector has already been used', () => {
patchJasmineBeforeEach();
expect(() => {
beforeEachProviders(() => [provide(FancyService, {useValue: new FancyService()})]);
})
.toThrowError('beforeEachProviders was called after the injector had been used ' +
'in a beforeEach or it block. This invalidates the test injector');
restoreJasmineBeforeEach();
});
});
});
});
//////// Testing Framework Bugs? ///// //////// Testing Framework Bugs? /////
import { HeroService } from './hero.service'; import { HeroService } from './hero.service';
@ -637,12 +497,12 @@ export class AnotherProvidersComp {
describe('tcb.overrideProviders', () => { describe('tcb.overrideProviders', () => {
it('Component must have at least one provider else crash', it('Component must have at least one provider else crash',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.overrideProviders( tcb.overrideProviders(
AnotherProvidersComp, AnotherProvidersComp,
[provide(HeroService, {useValue: {}})] [provide(HeroService, {useValue: {}})]
) )
.createAsync(AnotherProvidersComp); .createAsync(AnotherProvidersComp);
})); })));
}); });

View File

@ -3,6 +3,8 @@
import { Component, EventEmitter, Injectable, Input, Output, import { Component, EventEmitter, Injectable, Input, Output,
OnInit, OnChanges, OnDestroy, SimpleChange } from 'angular2/core'; OnInit, OnChanges, OnDestroy, SimpleChange } from 'angular2/core';
import { Observable } from 'rxjs/Rx';
// Let TypeScript know about the special SystemJS __moduleName variable // Let TypeScript know about the special SystemJS __moduleName variable
declare var __moduleName: string; declare var __moduleName: string;
@ -21,7 +23,16 @@ if (!__moduleName) {
@Injectable() @Injectable()
export class FancyService { export class FancyService {
value: string = 'real value'; value: string = 'real value';
getAsyncValue() { return Promise.resolve('async value'); } getAsyncValue() { return Promise.resolve('async value'); }
getObservableValue() { return Observable.of('observable value'); }
getTimeoutValue() {
return new Promise((resolve, reject) => { setTimeout(() => {resolve('timeout value'); }, 10); });
}
getObservableDelayValue() { return Observable.of('observable delay value').delay(10); }
} }
@Injectable() @Injectable()

View File

@ -8,18 +8,12 @@ import {
beforeEach, beforeEachProviders, beforeEach, beforeEachProviders,
describe, ddescribe, xdescribe, describe, ddescribe, xdescribe,
expect, it, iit, xit, expect, it, iit, xit,
inject, injectAsync, async, inject, TestComponentBuilder
TestComponentBuilder
} from 'angular2/testing'; } from 'angular2/testing';
import { Hero, HeroService, MockHeroService } from './mock-hero.service'; import { Hero, HeroService, MockHeroService } from './mock-hero.service';
import { Router, MockRouter } from './mock-router'; import { Router, MockRouter } from './mock-router';
interface Done {
(): void;
fail: (err: any) => void;
}
describe('DashboardComponent', () => { describe('DashboardComponent', () => {
//////// WITHOUT ANGULAR INVOLVED /////// //////// WITHOUT ANGULAR INVOLVED ///////
@ -45,7 +39,7 @@ describe('DashboardComponent', () => {
'should not have heroes until service promise resolves'); 'should not have heroes until service promise resolves');
}); });
it('should HAVE heroes after HeroService gets them', (done: Done) => { it('should HAVE heroes after HeroService gets them', (done: DoneFn) => {
comp.ngOnInit(); // ngOnInit -> getHeroes comp.ngOnInit(); // ngOnInit -> getHeroes
mockHeroService.lastPromise // the one from getHeroes mockHeroService.lastPromise // the one from getHeroes
.then(() => { .then(() => {
@ -85,55 +79,55 @@ describe('DashboardComponent', () => {
}); });
it('can instantiate it', it('can instantiate it',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(DashboardComponent); tcb.createAsync(DashboardComponent);
})); })));
it('should NOT have heroes before OnInit', it('should NOT have heroes before OnInit',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(DashboardComponent).then(fixture => { tcb.createAsync(DashboardComponent).then(fixture => {
comp = fixture.debugElement.componentInstance; comp = fixture.debugElement.componentInstance;
expect(comp.heroes.length).toEqual(0, expect(comp.heroes.length).toEqual(0,
'should not have heroes before OnInit'); 'should not have heroes before OnInit');
}); });
})); })));
it('should NOT have heroes immediately after OnInit', it('should NOT have heroes immediately after OnInit',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(DashboardComponent).then(fixture => { tcb.createAsync(DashboardComponent).then(fixture => {
comp = fixture.debugElement.componentInstance; comp = fixture.debugElement.componentInstance;
fixture.detectChanges(); // runs initial lifecycle hooks fixture.detectChanges(); // runs initial lifecycle hooks
expect(comp.heroes.length).toEqual(0, expect(comp.heroes.length).toEqual(0,
'should not have heroes until service promise resolves'); 'should not have heroes until service promise resolves');
}); });
})); })));
it('should HAVE heroes after HeroService gets them', it('should HAVE heroes after HeroService gets them',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(DashboardComponent).then(fixture => { tcb.createAsync(DashboardComponent).then(fixture => {
comp = fixture.debugElement.componentInstance; comp = fixture.debugElement.componentInstance;
fixture.detectChanges(); // runs ngOnInit -> getHeroes fixture.detectChanges(); // runs ngOnInit -> getHeroes
return mockHeroService.lastPromise // the one from getHeroes mockHeroService.lastPromise // the one from getHeroes
.then(() => { .then(() => {
expect(comp.heroes.length).toBeGreaterThan(0, expect(comp.heroes.length).toBeGreaterThan(0,
'should have heroes after service promise resolves'); 'should have heroes after service promise resolves');
}); });
}); });
})); })));
it('should DISPLAY heroes after HeroService gets them', it('should DISPLAY heroes after HeroService gets them',
injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => { async(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
return tcb.createAsync(DashboardComponent).then(fixture => { tcb.createAsync(DashboardComponent).then(fixture => {
comp = fixture.debugElement.componentInstance; comp = fixture.debugElement.componentInstance;
fixture.detectChanges(); // runs ngOnInit -> getHeroes fixture.detectChanges(); // runs ngOnInit -> getHeroes
return mockHeroService.lastPromise // the one from getHeroes mockHeroService.lastPromise // the one from getHeroes
.then(() => { .then(() => {
// Find and examine the displayed heroes // Find and examine the displayed heroes
@ -148,15 +142,15 @@ describe('DashboardComponent', () => {
}); });
}); });
})); })));
it('should tell ROUTER to navigate by hero id', it('should tell ROUTER to navigate by hero id',
injectAsync([TestComponentBuilder, Router], async(inject([TestComponentBuilder, Router],
(tcb: TestComponentBuilder, router: MockRouter) => { (tcb: TestComponentBuilder, router: MockRouter) => {
let spy = spyOn(router, 'navigate').and.callThrough(); let spy = spyOn(router, 'navigate').and.callThrough();
return tcb.createAsync(DashboardComponent).then(fixture => { tcb.createAsync(DashboardComponent).then(fixture => {
let hero: Hero = {id: 42, name: 'Abbracadabra' }; let hero: Hero = {id: 42, name: 'Abbracadabra' };
comp = fixture.debugElement.componentInstance; comp = fixture.debugElement.componentInstance;
comp.gotoDetail(hero); comp.gotoDetail(hero);
@ -166,6 +160,6 @@ describe('DashboardComponent', () => {
expect(linkParams[1].id).toEqual(hero.id, 'should nav to fake hero\'s id'); expect(linkParams[1].id).toEqual(hero.id, 'should nav to fake hero\'s id');
}); });
})); })));
}); });
}); });

View File

@ -3,7 +3,7 @@ import {
beforeEach, beforeEachProviders, withProviders, beforeEach, beforeEachProviders, withProviders,
describe, ddescribe, xdescribe, describe, ddescribe, xdescribe,
expect, it, iit, xit, expect, it, iit, xit,
inject, injectAsync, fakeAsync, TestComponentBuilder, tick async, inject, TestComponentBuilder
} from 'angular2/testing'; } from 'angular2/testing';
import { provide } from 'angular2/core'; import { provide } from 'angular2/core';
@ -81,45 +81,45 @@ describe('Http-HeroService (mockBackend)', () => {
response = new Response(options); response = new Response(options);
})); }));
it('should have expected fake heroes (then)', injectAsync([], () => { it('should have expected fake heroes (then)', async(inject([], () => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response)); backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
return service.getHeroes().toPromise() service.getHeroes().toPromise()
// .then(() => Promise.reject('deliberate')) // .then(() => Promise.reject('deliberate'))
.then(heroes => { .then(heroes => {
expect(heroes.length).toEqual(fakeHeroes.length, expect(heroes.length).toEqual(fakeHeroes.length,
'should have expected no. of heroes'); 'should have expected no. of heroes');
}); });
})); })));
it('should have expected fake heroes (Observable.do)', injectAsync([], () => { it('should have expected fake heroes (Observable.do)', async(inject([], () => {
backend.connections.subscribe((c: MockConnection) => c.mockRespond(response)); backend.connections.subscribe((c: MockConnection) => c.mockRespond(response));
return service.getHeroes() service.getHeroes()
.do(heroes => { .do(heroes => {
expect(heroes.length).toEqual(fakeHeroes.length, expect(heroes.length).toEqual(fakeHeroes.length,
'should have expected no. of heroes'); 'should have expected no. of heroes');
}) })
.toPromise(); .toPromise();
})); })));
it('should be OK returning no heroes', injectAsync([], () => { it('should be OK returning no heroes', async(inject([], () => {
let resp = new Response(new ResponseOptions({status: 200, body: {data: []}})); let resp = new Response(new ResponseOptions({status: 200, body: {data: []}}));
backend.connections.subscribe((c: MockConnection) => c.mockRespond(resp)); backend.connections.subscribe((c: MockConnection) => c.mockRespond(resp));
return service.getHeroes() service.getHeroes()
.do(heroes => { .do(heroes => {
expect(heroes.length).toEqual(0, 'should have no heroes'); expect(heroes.length).toEqual(0, 'should have no heroes');
}) })
.toPromise(); .toPromise();
})); })));
it('should treat 404 as an Observable error', injectAsync([], () => { it('should treat 404 as an Observable error', async(inject([], () => {
let resp = new Response(new ResponseOptions({status: 404})); let resp = new Response(new ResponseOptions({status: 404}));
backend.connections.subscribe((c: MockConnection) => c.mockRespond(resp)); backend.connections.subscribe((c: MockConnection) => c.mockRespond(resp));
return service.getHeroes() service.getHeroes()
.do(heroes => { .do(heroes => {
fail('should not respond with heroes'); fail('should not respond with heroes');
}) })
@ -128,6 +128,6 @@ describe('Http-HeroService (mockBackend)', () => {
return Observable.of(null); // failure is the expected test result return Observable.of(null); // failure is the expected test result
}) })
.toPromise(); .toPromise();
})); })));
}); });
}); });

View File

@ -5,13 +5,22 @@ module.exports = function () {
return { return {
files: [ files: [
{pattern: 'node_modules/es6-shim/es6-shim.js', instrument: false}, // System.js for module loading
{pattern: 'node_modules/systemjs/dist/system-polyfills.js', instrument: false}, {pattern: 'node_modules/systemjs/dist/system-polyfills.js', instrument: false},
{pattern: 'node_modules/systemjs/dist/system.js', instrument: false}, {pattern: 'node_modules/systemjs/dist/system.js', instrument: false},
{pattern: 'node_modules/reflect-metadata/Reflect.js', instrument: false},
{pattern: 'node_modules/zone.js/dist/zone.js', instrument: false}, // Polyfills
{pattern: 'node_modules/zone.js/dist/long-stack-trace-zone.js', instrument: false}, {pattern: 'node_modules/es6-shim/es6-shim.js', instrument: false},
{pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', instrument: false},
// Zone.js dependencies
// Note - do not include zone.js itself or long-stack-trace-zone.js` here as
// they are included already in angular2-polyfills
{pattern: 'node_modules/zone.js/dist/jasmine-patch.js', instrument: false}, {pattern: 'node_modules/zone.js/dist/jasmine-patch.js', instrument: false},
{pattern: 'node_modules/zone.js/dist/async-test.js', instrument: false},
{pattern: 'node_modules/zone.js/dist/fake-async-test.js', instrument: false},
// Rx.js, Angular 2 itself, and the testing library not here because loaded by systemjs
{pattern: 'app/**/*+(ts|html|css)', load: false}, {pattern: 'app/**/*+(ts|html|css)', load: false},
{pattern: 'app/**/*.spec.ts', ignore: true} {pattern: 'app/**/*.spec.ts', ignore: true}