feat(tests): manage asynchronous tests using zones

Instead of using injectAsync and returning a promise, use the `async` function
to wrap tests. This will run the test inside a zone which does not complete
the test until all asynchronous tasks have been completed.

`async` may be used with the `inject` function, or separately.

BREAKING CHANGE:

`injectAsync` is now deprecated. Instead, use the `async` function
to wrap any asynchronous tests.

Before:
```
it('should wait for returned promises', injectAsync([FancyService], (service) => {
  return service.getAsyncValue().then((value) => { expect(value).toEqual('async value'); });
}));

it('should wait for returned promises', injectAsync([], () => {
  return somePromise.then(() => { expect(true).toEqual(true); });
}));
```

After:
```
it('should wait for returned promises', async(inject([FancyService], (service) => {
  service.getAsyncValue().then((value) => { expect(value).toEqual('async value'); });
})));

// Note that if there is no injection, we no longer need `inject` OR `injectAsync`.
it('should wait for returned promises', async(() => {
  somePromise.then() => { expect(true).toEqual(true); });
}));
```

Closes #7735
This commit is contained in:
Julie Ralph
2016-03-23 10:59:38 -07:00
committed by Robert Messerle
parent ecb9bb96f0
commit 8490921fb3
11 changed files with 289 additions and 254 deletions
@@ -130,6 +130,7 @@ export class InjectSetupWrapper {
return new FunctionWithParamTokens(tokens, fn, false, this._providers);
}
/** @Deprecated {use async(withProviders().inject())} */
injectAsync(tokens: any[], fn: Function): FunctionWithParamTokens {
return new FunctionWithParamTokens(tokens, fn, true, this._providers);
}
@@ -140,6 +141,8 @@ export function withProviders(providers: () => any) {
}
/**
* @Deprecated {use async(inject())}
*
* Allows injecting dependencies in `beforeEach()` and `it()`. The test must return
* a promise which will resolve when all asynchronous activity is complete.
*
@@ -161,6 +164,32 @@ export function injectAsync(tokens: any[], fn: Function): FunctionWithParamToken
return new FunctionWithParamTokens(tokens, fn, true);
}
/**
* 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
* to wrap an {@link inject} call.
*
* Example:
*
* ```
* it('...', async(inject([AClass], (object) => {
* object.doSomething.then(() => {
* expect(...);
* })
* });
* ```
*/
export function async(fn: Function | FunctionWithParamTokens): FunctionWithParamTokens {
if (fn instanceof FunctionWithParamTokens) {
fn.isAsync = true;
return fn;
} else if (fn instanceof Function) {
return new FunctionWithParamTokens([], fn, true);
} else {
throw new BaseException('argument to async must be a function or inject(<Function>)');
}
}
function emptyArray(): Array<any> {
return [];
}
+16 -44
View File
@@ -9,12 +9,13 @@ import {bind} from 'angular2/core';
import {
FunctionWithParamTokens,
inject,
async,
injectAsync,
TestInjector,
getTestInjector
} from './test_injector';
export {inject, injectAsync} from './test_injector';
export {inject, async, injectAsync} from './test_injector';
export {expect, NgMatchers} from './matchers';
@@ -122,6 +123,14 @@ export function beforeEachProviders(fn): void {
});
}
function runInAsyncTestZone(fnToExecute, finishCallback: Function, failCallback: Function,
testName = ''): any {
var AsyncTestZoneSpec = Zone['AsyncTestZoneSpec'];
var testZoneSpec = new AsyncTestZoneSpec(finishCallback, failCallback, testName);
var testZone = Zone.current.fork(testZoneSpec);
return testZone.run(fnToExecute);
}
function _isPromiseLike(input): boolean {
return input && !!(input.then);
}
@@ -129,30 +138,13 @@ function _isPromiseLike(input): boolean {
function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | AnyTestFn,
testTimeOut: number): void {
var timeOut = testTimeOut;
if (testFn instanceof FunctionWithParamTokens) {
let testFnT = testFn;
jsmFn(name, (done) => {
var returnedTestValue;
try {
returnedTestValue = testInjector.execute(testFnT);
} catch (err) {
done.fail(err);
return;
}
if (testFnT.isAsync) {
if (_isPromiseLike(returnedTestValue)) {
(<Promise<any>>returnedTestValue).then(() => { done(); }, (err) => { done.fail(err); });
} else {
done.fail('Error: injectAsync was expected to return a promise, but the ' +
' returned value was: ' + returnedTestValue);
}
runInAsyncTestZone(() => testInjector.execute(testFnT), done, done.fail, name);
} else {
if (!(returnedTestValue === undefined)) {
done.fail('Error: inject returned a value. Did you mean to use injectAsync? Returned ' +
'value was: ' + returnedTestValue);
}
testInjector.execute(testFnT);
done();
}
}, timeOut);
@@ -166,8 +158,6 @@ function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | An
* Wrapper around Jasmine beforeEach function.
*
* beforeEach may be used with the `inject` function to fetch dependencies.
* The test will automatically wait for any asynchronous calls inside the
* injected test function to complete.
*
* See http://jasmine.github.io/ for more details.
*
@@ -181,26 +171,10 @@ export function beforeEach(fn: FunctionWithParamTokens | AnyTestFn): void {
// }));`
let fnT = fn;
jsmBeforeEach((done) => {
var returnedTestValue;
try {
returnedTestValue = testInjector.execute(fnT);
} catch (err) {
done.fail(err);
return;
}
if (fnT.isAsync) {
if (_isPromiseLike(returnedTestValue)) {
(<Promise<any>>returnedTestValue).then(() => { done(); }, (err) => { done.fail(err); });
} else {
done.fail('Error: injectAsync was expected to return a promise, but the ' +
' returned value was: ' + returnedTestValue);
}
runInAsyncTestZone(() => testInjector.execute(fnT), done, done.fail, 'beforeEach');
} else {
if (!(returnedTestValue === undefined)) {
done.fail('Error: inject returned a value. Did you mean to use injectAsync? Returned ' +
'value was: ' + returnedTestValue);
}
testInjector.execute(fnT);
done();
}
});
@@ -217,10 +191,8 @@ export function beforeEach(fn: FunctionWithParamTokens | AnyTestFn): void {
/**
* Define a single test case with the given test name and execution function.
*
* The test function can be either a synchronous function, an asynchronous function
* that takes a completion callback, or an injected function created via {@link inject}
* or {@link injectAsync}. The test will automatically wait for any asynchronous calls
* inside the injected test function to complete.
* The test function can be either a synchronous function, the result of {@link async},
* or an injected function created via {@link inject}.
*
* Wrapper around Jasmine it function. See http://jasmine.github.io/ for more details.
*