From 033f1fdee5ebafe5d1e4475346dbd8228e96e1cb Mon Sep 17 00:00:00 2001 From: Julie Ralph Date: Wed, 22 Jun 2016 22:25:33 -0700 Subject: [PATCH] refactor(testing): remove wrapping of Jasmine functions Instead, the async function now determines whether it should return a promise or instead call a done function parameter. Importing Jasmine functions from `@angular/core/testing` is no longer necessary and is now deprecated. Additionally, beforeEachProviders is also deprecated, as it is specific to the testing framework. Instead, use the new addProviders method directly. Before: ```js import {beforeEachProviders, it, describe, inject} from 'angular2/testing/core'; describe('my code', () => { beforeEachProviders(() => [MyService]); it('does stuff', inject([MyService], (service) => { // actual test }); }); ``` After: ```js import {addProviders, inject} from 'angular2/testing/core'; describe('my code', () => { beforeEach(() => { addProviders([MyService]); }); it('does stuff', inject([MyService], (service) => { // actual test }); }); ``` --- modules/@angular/core/testing/async.ts | 44 +++- modules/@angular/core/testing/fake_async.ts | 2 +- .../@angular/core/testing/test_injector.ts | 4 +- modules/@angular/core/testing/testing.ts | 201 ++++++------------ .../test/testing_public_spec.ts | 20 +- tools/public_api_guard/core/testing.d.ts | 24 ++- 6 files changed, 127 insertions(+), 168 deletions(-) diff --git a/modules/@angular/core/testing/async.ts b/modules/@angular/core/testing/async.ts index 8905cb6052371..5b604814125fc 100644 --- a/modules/@angular/core/testing/async.ts +++ b/modules/@angular/core/testing/async.ts @@ -6,6 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ +declare var global: any; + +var _global = (typeof window === 'undefined' ? global : window); + /** * Wraps a test function in an asynchronous test zone. The test will automatically * complete when all asynchronous calls within this zone are done. Can be used @@ -21,16 +25,36 @@ * }); * ``` */ -export function async(fn: Function): Function { +export function async(fn: Function): (done: any) => any { + // If we're running using the Jasmine test framework, adapt to call the 'done' + // function when asynchronous activity is finished. + if (_global.jasmine) { + return (done: any) => { + runInTestZone(fn, done, (err: string | Error) => { + if (typeof err === 'string') { + return done.fail(new Error(err)); + } else { + done.fail(err); + } + }); + }; + } + // Otherwise, return a promise which will resolve when asynchronous activity + // is finished. This will be correctly consumed by the Mocha framework with + // it('...', async(myFn)); or can be used in a custom framework. return () => new Promise((finishCallback, failCallback) => { - var AsyncTestZoneSpec = (Zone as any /** TODO #9100 */)['AsyncTestZoneSpec']; - if (AsyncTestZoneSpec === undefined) { - throw new Error( - 'AsyncTestZoneSpec is needed for the async() test helper but could not be found. ' + - 'Please make sure that your environment includes zone.js/dist/async-test.js'); - } - var testZoneSpec = new AsyncTestZoneSpec(finishCallback, failCallback, 'test'); - var testZone = Zone.current.fork(testZoneSpec); - return testZone.run(fn); + runInTestZone(fn, finishCallback, failCallback); }); } + +function runInTestZone(fn: Function, finishCallback: Function, failCallback: Function) { + var AsyncTestZoneSpec = (Zone as any /** TODO #9100 */)['AsyncTestZoneSpec']; + if (AsyncTestZoneSpec === undefined) { + throw new Error( + 'AsyncTestZoneSpec is needed for the async() test helper but could not be found. ' + + 'Please make sure that your environment includes zone.js/dist/async-test.js'); + } + var testZoneSpec = new AsyncTestZoneSpec(finishCallback, failCallback, 'test'); + var testZone = Zone.current.fork(testZoneSpec); + return testZone.run(fn); +} diff --git a/modules/@angular/core/testing/fake_async.ts b/modules/@angular/core/testing/fake_async.ts index d4676f5afbf69..95db994ba3478 100644 --- a/modules/@angular/core/testing/fake_async.ts +++ b/modules/@angular/core/testing/fake_async.ts @@ -26,7 +26,7 @@ let _FakeAsyncTestZoneSpecType = (Zone as any /** TODO #9100 */)['FakeAsyncTestZ * @param fn * @returns {Function} The function wrapped to be executed in the fakeAsync zone */ -export function fakeAsync(fn: Function): Function { +export function fakeAsync(fn: Function): (...args: any[]) => any { if (Zone.current.get('FakeAsyncTestZoneSpec') != null) { throw new BaseException('fakeAsync() calls can not be nested'); } diff --git a/modules/@angular/core/testing/test_injector.ts b/modules/@angular/core/testing/test_injector.ts index 1af863b84467e..3006526ab5594 100644 --- a/modules/@angular/core/testing/test_injector.ts +++ b/modules/@angular/core/testing/test_injector.ts @@ -132,7 +132,7 @@ export function resetBaseTestProviders() { * becomes `it('...', @Inject (object: AClass, async: AsyncTestCompleter) => { ... });` * */ -export function inject(tokens: any[], fn: Function): Function { +export function inject(tokens: any[], fn: Function): () => any { let testInjector = getTestInjector(); if (tokens.indexOf(AsyncTestCompleter) >= 0) { // Return an async test method that returns a Promise if AsyncTestCompleter is one of the @@ -158,7 +158,7 @@ export class InjectSetupWrapper { } } - inject(tokens: any[], fn: Function): Function { + inject(tokens: any[], fn: Function): () => any { return () => { this._addProviders(); return inject_impl(tokens, fn)(); diff --git a/modules/@angular/core/testing/testing.ts b/modules/@angular/core/testing/testing.ts index 99864707b87c4..4cc02512daa32 100644 --- a/modules/@angular/core/testing/testing.ts +++ b/modules/@angular/core/testing/testing.ts @@ -7,10 +7,10 @@ */ /** - * Public Test Library for unit testing Angular2 Applications. Uses the - * Jasmine framework. + * Public Test Library for unit testing Angular2 Applications. Assumes that you are running + * with Jasmine, Mocha, or a similar framework which exports a beforeEach function and + * allows tests to be asynchronous by either returning a promise or using a 'done' parameter. */ -import {isPromise, isString} from '../src/facade/lang'; import {TestInjector, getTestInjector} from './test_injector'; @@ -18,188 +18,121 @@ declare var global: any; var _global = (typeof window === 'undefined' ? global : window); +/** + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. + * + * See http://jasmine.github.io/ for more details. + */ export var expect: Function = _global.expect; /** - * Run a function (with an optional asynchronous callback) after each test case. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * * See http://jasmine.github.io/ for more details. - * - * ## Example: - * - * {@example testing/ts/testing.ts region='afterEach'} */ export var afterEach: Function = _global.afterEach; /** - * Group test cases together under a common description prefix. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * * See http://jasmine.github.io/ for more details. - * - * ## Example: - * - * {@example testing/ts/testing.ts region='describeIt'} */ export var describe: Function = _global.describe; /** - * See {@link fdescribe}. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. + * + * See http://jasmine.github.io/ for more details. */ -export var ddescribe: Function = _global.fdescribe; +export var fdescribe = _global.fdescribe; /** - * Like {@link describe}, but instructs the test runner to only run - * the test cases in this group. This is useful for debugging. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * * See http://jasmine.github.io/ for more details. - * - * ## Example: - * - * {@example testing/ts/testing.ts region='fdescribe'} */ -export var fdescribe: Function = _global.fdescribe; +export var ddescribe = _global.ddescribe; /** - * Like {@link describe}, but instructs the test runner to exclude - * this group of test cases from execution. This is useful for - * debugging, or for excluding broken tests until they can be fixed. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * * See http://jasmine.github.io/ for more details. - * - * ## Example: - * - * {@example testing/ts/testing.ts region='xdescribe'} */ export var xdescribe: Function = _global.xdescribe; -var jsmBeforeEach = _global.beforeEach; -var jsmIt = _global.it; -var jsmIIt = _global.fit; -var jsmXIt = _global.xit; - -// TODO(juliemr): override the globals and make them throw with a useful message. - -var testInjector: TestInjector = getTestInjector(); - -// Reset the test providers before each test. -jsmBeforeEach(() => { testInjector.reset(); }); - /** - * Allows overriding default providers of the test injector, - * which are defined in test_injector.js. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * - * The given function must return a list of DI providers. - * - * ## Example: - * - * {@example testing/ts/testing.ts region='beforeEachProviders'} + * See http://jasmine.github.io/ for more details. */ -export function beforeEachProviders(fn: () => Array): void { - jsmBeforeEach(() => { - var providers = fn(); - if (!providers) return; - try { - testInjector.addProviders(providers); - } catch (e) { - throw new Error( - 'beforeEachProviders was called after the injector had ' + - 'been used in a beforeEach or it block. This invalidates the ' + - 'test injector'); - } - }); -} - -function _wrapTestFn(fn: Function) { - // Wraps a test or beforeEach function to handle synchronous and asynchronous execution. - return (done: any) => { - if (fn.length === 0) { - let retVal = fn(); - if (isPromise(retVal)) { - // Asynchronous test function - wait for completion. - (>retVal).then(done, (err) => { - if (isString(err)) { - return done.fail(new Error(err)); - } - return done.fail(err); - }); - } else { - // Synchronous test function - complete immediately. - done(); - } - } else { - // Asynchronous test function that takes "done" as parameter. - fn(done); - } - }; -} - -function _it(jsmFn: Function, name: string, testFn: Function, testTimeOut: number): void { - jsmFn(name, _wrapTestFn(testFn), testTimeOut); -} +export var beforeEach = _global.beforeEach; /** - * Wrapper around Jasmine beforeEach function. - * - * beforeEach may be used with the `inject` function to fetch dependencies. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * * See http://jasmine.github.io/ for more details. - * - * ## Example: - * - * {@example testing/ts/testing.ts region='beforeEach'} */ -export function beforeEach(fn: Function): void { - jsmBeforeEach(_wrapTestFn(fn)); -} +export var it = _global.it; /** - * Define a single test case with the given test name and execution function. - * - * The test function can be either a synchronous function, the result of {@link async}, - * or an injected function created via {@link inject}. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * - * Wrapper around Jasmine it function. See http://jasmine.github.io/ for more details. - * - * ## Example: - * - * {@example testing/ts/testing.ts region='describeIt'} + * See http://jasmine.github.io/ for more details. */ -export function it(name: string, fn: Function, timeOut: number = null): void { - return _it(jsmIt, name, fn, timeOut); -} +export var fit = _global.fit; /** - * Like {@link it}, but instructs the test runner to exclude this test - * entirely. Useful for debugging or for excluding broken tests until - * they can be fixed. + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * - * Wrapper around Jasmine xit function. See http://jasmine.github.io/ for more details. - * - * ## Example: + * See http://jasmine.github.io/ for more details. + */ +export var iit = _global.fit; + +/** + * @deprecated you no longer need to import jasmine functions from @angular/core/testing. Simply use + * the globals. * - * {@example testing/ts/testing.ts region='xit'} + * See http://jasmine.github.io/ for more details. */ -export function xit(name: string, fn: Function, timeOut: number = null): void { - return _it(jsmXIt, name, fn, timeOut); +export var xit = _global.xit; + + +var testInjector: TestInjector = getTestInjector(); + +// Reset the test providers before each test. +if (_global.beforeEach) { + beforeEach(() => { testInjector.reset(); }); } /** - * See {@link fit}. + * Allows overriding default providers of the test injector, + * which are defined in test_injector.js */ -export function iit(name: string, fn: Function, timeOut: number = null): void { - return _it(jsmIIt, name, fn, timeOut); +export function addProviders(providers: Array): void { + if (!providers) return; + try { + testInjector.addProviders(providers); + } catch (e) { + throw new Error( + 'addProviders can\'t be called after the injector has been already created for this test. ' + + 'This is most likely because you\'ve already used the injector to inject a beforeEach or the ' + + 'current `it` function.'); + } } /** - * Like {@link it}, but instructs the test runner to only run this test. - * Useful for debugging. - * - * Wrapper around Jasmine fit function. See http://jasmine.github.io/ for more details. - * - * ## Example: - * - * {@example testing/ts/testing.ts region='fit'} + * @deprecated Use beforeEach(() => addProviders()) */ -export function fit(name: string, fn: Function, timeOut: number = null): void { - return _it(jsmIIt, name, fn, timeOut); +export function beforeEachProviders(fn: () => Array): void { + beforeEach(() => { addProviders(fn()); }); } diff --git a/modules/@angular/platform-browser/test/testing_public_spec.ts b/modules/@angular/platform-browser/test/testing_public_spec.ts index 2504023b590a7..c60331bdaeab2 100644 --- a/modules/@angular/platform-browser/test/testing_public_spec.ts +++ b/modules/@angular/platform-browser/test/testing_public_spec.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing'; - -import {fakeAsync, async, withProviders, tick,} from '@angular/core/testing'; +import {addProviders, inject, fakeAsync, async, withProviders, tick,} from '@angular/core/testing'; import {TestComponentBuilder} from '@angular/compiler/testing'; +import {expect} from '@angular/platform-browser/testing/matchers'; import {Injectable, provide, Component, ViewMetadata} from '@angular/core'; import {NgIf} from '@angular/common'; import {PromiseWrapper} from '../../http/src/facade/promise'; @@ -127,7 +126,7 @@ export function main() { describe('using the test injector with the inject helper', () => { describe('setting up Providers', () => { - beforeEachProviders(() => [{provide: FancyService, useValue: new FancyService()}]); + beforeEach(() => addProviders([{provide: FancyService, useValue: new FancyService()}])); it('should use set up providers', inject([FancyService], (service: any /** TODO #9100 */) => { expect(service.value).toEqual('real value'); @@ -273,23 +272,24 @@ export function main() { restoreJasmineIt(); }); - describe('using beforeEachProviders', () => { - beforeEachProviders(() => [{provide: FancyService, useValue: new FancyService()}]); + describe('using addProviders', () => { + beforeEach(() => addProviders([{provide: FancyService, useValue: new FancyService()}])); beforeEach(inject([FancyService], (service: any /** TODO #9100 */) => { expect(service.value).toEqual('real value'); })); - describe('nested beforeEachProviders', () => { + describe('nested addProviders', () => { it('should fail when the injector has already been used', () => { patchJasmineBeforeEach(); expect(() => { - beforeEachProviders(() => [{provide: FancyService, useValue: new FancyService()}]); + beforeEach(() => addProviders([{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'); + 'addProviders can\'t be called after the injector has been already created for this test. ' + + 'This is most likely because you\'ve already used the injector to inject a beforeEach or the ' + + 'current `it` function.'); restoreJasmineBeforeEach(); }); }); diff --git a/tools/public_api_guard/core/testing.d.ts b/tools/public_api_guard/core/testing.d.ts index b664f5476a064..8ac98fb651fa7 100644 --- a/tools/public_api_guard/core/testing.d.ts +++ b/tools/public_api_guard/core/testing.d.ts @@ -1,8 +1,10 @@ +export declare function addProviders(providers: Array): void; + export declare var afterEach: Function; -export declare function async(fn: Function): Function; +export declare function async(fn: Function): (done: any) => any; -export declare function beforeEach(fn: Function): void; +export declare var beforeEach: any; export declare function beforeEachProviders(fn: () => Array): void; @@ -27,7 +29,7 @@ export declare var ComponentFixtureAutoDetect: OpaqueToken; export declare var ComponentFixtureNoNgZone: OpaqueToken; -export declare var ddescribe: Function; +export declare var ddescribe: any; export declare var describe: Function; @@ -35,26 +37,26 @@ export declare function discardPeriodicTasks(): void; export declare var expect: Function; -export declare function fakeAsync(fn: Function): Function; +export declare function fakeAsync(fn: Function): (...args: any[]) => any; -export declare var fdescribe: Function; +export declare var fdescribe: any; -export declare function fit(name: string, fn: Function, timeOut?: number): void; +export declare var fit: any; export declare function flushMicrotasks(): void; export declare function getTestInjector(): TestInjector; -export declare function iit(name: string, fn: Function, timeOut?: number): void; +export declare var iit: any; -export declare function inject(tokens: any[], fn: Function): Function; +export declare function inject(tokens: any[], fn: Function): () => any; export declare class InjectSetupWrapper { constructor(_providers: () => any); - inject(tokens: any[], fn: Function): Function; + inject(tokens: any[], fn: Function): () => any; } -export declare function it(name: string, fn: Function, timeOut?: number): void; +export declare var it: any; export declare function resetBaseTestProviders(): void; @@ -95,4 +97,4 @@ export declare function withProviders(providers: () => any): InjectSetupWrapper; export declare var xdescribe: Function; -export declare function xit(name: string, fn: Function, timeOut?: number): void; +export declare var xit: any;