X Tutup
Skip to content

Commit 0c9596a

Browse files
juliemrvsavkin
authored andcommitted
feat(testing): use zones to avoid the need for injectAsync
Use a zone counting timeouts and microtasks to determine when a test is finished, instead of requiring the test writer to use injectAsync and return a promise. See #5322
1 parent a8c3b9d commit 0c9596a

File tree

3 files changed

+204
-117
lines changed

3 files changed

+204
-117
lines changed

modules/angular2/src/testing/test_injector.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ export function createTestInjector(providers: Array<Type | Provider | any[]>): I
123123
}
124124

125125
/**
126-
* Allows injecting dependencies in `beforeEach()` and `it()`.
126+
* Allows injecting dependencies in `beforeEach()` and `it()`. When using with the
127+
* `angular2/testing` library, the test function will be run within a zone and will
128+
* automatically complete when all asynchronous tests have finished.
127129
*
128130
* Example:
129131
*
@@ -133,17 +135,14 @@ export function createTestInjector(providers: Array<Type | Provider | any[]>): I
133135
* // ...
134136
* }));
135137
*
136-
* it('...', inject([AClass, AsyncTestCompleter], (object, async) => {
138+
* it('...', inject([AClass], (object) => {
137139
* object.doSomething().then(() => {
138140
* expect(...);
139-
* async.done();
140141
* });
141142
* })
142143
* ```
143144
*
144145
* Notes:
145-
* - injecting an `AsyncTestCompleter` allow completing async tests - this is the equivalent of
146-
* adding a `done` parameter in Jasmine,
147146
* - inject is currently a function because of some Traceur limitation the syntax should eventually
148147
* becomes `it('...', @Inject (object: AClass, async: AsyncTestCompleter) => { ... });`
149148
*
@@ -155,6 +154,9 @@ export function inject(tokens: any[], fn: Function): FunctionWithParamTokens {
155154
return new FunctionWithParamTokens(tokens, fn, false);
156155
}
157156

157+
/**
158+
* @deprecated Use inject instead, which now supports both synchronous and asynchronous tests.
159+
*/
158160
export function injectAsync(tokens: any[], fn: Function): FunctionWithParamTokens {
159161
return new FunctionWithParamTokens(tokens, fn, true);
160162
}

modules/angular2/src/testing/testing.ts

Lines changed: 135 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Jasmine framework.
44
*/
55
import {global} from 'angular2/src/facade/lang';
6-
6+
import {ListWrapper} from 'angular2/src/facade/collection';
77
import {bind} from 'angular2/src/core/di';
88

99
import {createTestInjector, FunctionWithParamTokens, inject, injectAsync} from './test_injector';
@@ -14,10 +14,29 @@ export {expect, NgMatchers} from './matchers';
1414

1515
var _global: jasmine.GlobalPolluter = <any>(typeof window === 'undefined' ? global : window);
1616

17+
/**
18+
* See http://jasmine.github.io/
19+
*/
1720
export var afterEach: Function = _global.afterEach;
21+
22+
/**
23+
* See http://jasmine.github.io/
24+
*/
1825
export var describe: Function = _global.describe;
26+
27+
/**
28+
* See http://jasmine.github.io/
29+
*/
1930
export var ddescribe: Function = _global.fdescribe;
31+
32+
/**
33+
* See http://jasmine.github.io/
34+
*/
2035
export var fdescribe: Function = _global.fdescribe;
36+
37+
/**
38+
* See http://jasmine.github.io/
39+
*/
2140
export var xdescribe: Function = _global.xdescribe;
2241

2342
export type SyncTestFn = () => void;
@@ -40,16 +59,18 @@ jsmBeforeEach(() => {
4059

4160
/**
4261
* Allows overriding default providers of the test injector,
43-
* defined in test_injector.js.
62+
* which are defined in test_injector.js.
4463
*
4564
* The given function must return a list of DI providers.
4665
*
4766
* Example:
4867
*
68+
* ```
4969
* beforeEachProviders(() => [
5070
* bind(Compiler).toClass(MockCompiler),
5171
* bind(SomeToken).toValue(myValue),
5272
* ]);
73+
* ```
5374
*/
5475
export function beforeEachProviders(fn): void {
5576
jsmBeforeEach(() => {
@@ -68,72 +89,101 @@ function _isPromiseLike(input): boolean {
6889
return input && !!(input.then);
6990
}
7091

92+
function runInTestZone(fnToExecute, finishCallback, failCallback): any {
93+
var pendingMicrotasks = 0;
94+
var pendingTimeouts = [];
95+
96+
var ngTestZone = (<Zone>global.zone)
97+
.fork({
98+
onError: function(e) { failCallback(e); },
99+
'$run': function(parentRun) {
100+
return function() {
101+
try {
102+
return parentRun.apply(this, arguments);
103+
} finally {
104+
if (pendingMicrotasks == 0 && pendingTimeouts.length == 0) {
105+
finishCallback();
106+
}
107+
}
108+
};
109+
},
110+
'$scheduleMicrotask': function(parentScheduleMicrotask) {
111+
return function(fn) {
112+
pendingMicrotasks++;
113+
var microtask = function() {
114+
try {
115+
fn();
116+
} finally {
117+
pendingMicrotasks--;
118+
}
119+
};
120+
parentScheduleMicrotask.call(this, microtask);
121+
};
122+
},
123+
'$setTimeout': function(parentSetTimeout) {
124+
return function(fn: Function, delay: number, ...args) {
125+
var id;
126+
var cb = function() {
127+
fn();
128+
ListWrapper.remove(pendingTimeouts, id);
129+
};
130+
id = parentSetTimeout(cb, delay, args);
131+
pendingTimeouts.push(id);
132+
return id;
133+
};
134+
},
135+
'$clearTimeout': function(parentClearTimeout) {
136+
return function(id: number) {
137+
parentClearTimeout(id);
138+
ListWrapper.remove(pendingTimeouts, id);
139+
};
140+
},
141+
});
142+
143+
return ngTestZone.run(fnToExecute);
144+
}
145+
71146
function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | AnyTestFn,
72147
testTimeOut: number): void {
73148
var timeOut = testTimeOut;
74149

75150
if (testFn instanceof FunctionWithParamTokens) {
76-
// The test case uses inject(). ie `it('test', inject([ClassA], (a) => { ...
77-
// }));`
78-
if (testFn.isAsync) {
79-
jsmFn(name, (done) => {
80-
if (!injector) {
81-
injector = createTestInjector(testProviders);
82-
}
83-
var returned = testFn.execute(injector);
84-
if (_isPromiseLike(returned)) {
85-
returned.then(done, done.fail);
86-
} else {
87-
done.fail('Error: injectAsync was expected to return a promise, but the ' +
88-
' returned value was: ' + returned);
89-
}
90-
}, timeOut);
91-
} else {
92-
jsmFn(name, () => {
93-
if (!injector) {
94-
injector = createTestInjector(testProviders);
95-
}
96-
var returned = testFn.execute(injector);
97-
if (_isPromiseLike(returned)) {
98-
throw new Error('inject returned a promise. Did you mean to use injectAsync?');
99-
};
100-
});
101-
}
151+
jsmFn(name, (done) => {
152+
if (!injector) {
153+
injector = createTestInjector(testProviders);
154+
}
155+
156+
var returnedTestValue = runInTestZone(() => testFn.execute(injector), done, done.fail);
157+
if (_isPromiseLike(returnedTestValue)) {
158+
(<Promise<any>>returnedTestValue).then(null, (err) => { done.fail(err); });
159+
}
160+
}, timeOut);
102161
} else {
103162
// The test case doesn't use inject(). ie `it('test', (done) => { ... }));`
104163
jsmFn(name, testFn, timeOut);
105164
}
106165
}
107166

108-
167+
/**
168+
* Wrapper around Jasmine beforeEach function.
169+
* See http://jasmine.github.io/
170+
*
171+
* beforeEach may be used with the `inject` function to fetch dependencies.
172+
* The test will automatically wait for any asynchronous calls inside the
173+
* injected test function to complete.
174+
*/
109175
export function beforeEach(fn: FunctionWithParamTokens | AnyTestFn): void {
110176
if (fn instanceof FunctionWithParamTokens) {
111177
// The test case uses inject(). ie `beforeEach(inject([ClassA], (a) => { ...
112178
// }));`
113-
if (fn.isAsync) {
114-
jsmBeforeEach((done) => {
115-
if (!injector) {
116-
injector = createTestInjector(testProviders);
117-
}
118-
var returned = fn.execute(injector);
119-
if (_isPromiseLike(returned)) {
120-
returned.then(done, done.fail);
121-
} else {
122-
done.fail('Error: injectAsync was expected to return a promise, but the ' +
123-
' returned value was: ' + returned);
124-
}
125-
});
126-
} else {
127-
jsmBeforeEach(() => {
128-
if (!injector) {
129-
injector = createTestInjector(testProviders);
130-
}
131-
var returned = fn.execute(injector);
132-
if (_isPromiseLike(returned)) {
133-
throw new Error('inject returned a promise. Did you mean to use injectAsync?');
134-
};
135-
});
136-
}
179+
180+
jsmBeforeEach((done) => {
181+
if (!injector) {
182+
injector = createTestInjector(testProviders);
183+
}
184+
185+
runInTestZone(() => fn.execute(injector), done, done.fail);
186+
});
137187
} else {
138188
// The test case doesn't use inject(). ie `beforeEach((done) => { ... }));`
139189
if ((<any>fn).length === 0) {
@@ -144,21 +194,53 @@ export function beforeEach(fn: FunctionWithParamTokens | AnyTestFn): void {
144194
}
145195
}
146196

197+
/**
198+
* Wrapper around Jasmine it function.
199+
* See http://jasmine.github.io/
200+
*
201+
* it may be used with the `inject` function to fetch dependencies.
202+
* The test will automatically wait for any asynchronous calls inside the
203+
* injected test function to complete.
204+
*/
147205
export function it(name: string, fn: FunctionWithParamTokens | AnyTestFn,
148206
timeOut: number = null): void {
149207
return _it(jsmIt, name, fn, timeOut);
150208
}
151209

210+
/**
211+
* Wrapper around Jasmine xit (skipped it) function.
212+
* See http://jasmine.github.io/
213+
*
214+
* it may be used with the `inject` function to fetch dependencies.
215+
* The test will automatically wait for any asynchronous calls inside the
216+
* injected test function to complete.
217+
*/
152218
export function xit(name: string, fn: FunctionWithParamTokens | AnyTestFn,
153219
timeOut: number = null): void {
154220
return _it(jsmXIt, name, fn, timeOut);
155221
}
156222

223+
/**
224+
* Wrapper around Jasmine iit (focused it) function.
225+
* See http://jasmine.github.io/
226+
*
227+
* it may be used with the `inject` function to fetch dependencies.
228+
* The test will automatically wait for any asynchronous calls inside the
229+
* injected test function to complete.
230+
*/
157231
export function iit(name: string, fn: FunctionWithParamTokens | AnyTestFn,
158232
timeOut: number = null): void {
159233
return _it(jsmIIt, name, fn, timeOut);
160234
}
161235

236+
/**
237+
* Wrapper around Jasmine fit (focused it) function.
238+
* See http://jasmine.github.io/
239+
*
240+
* it may be used with the `inject` function to fetch dependencies.
241+
* The test will automatically wait for any asynchronous calls inside the
242+
* injected test function to complete.
243+
*/
162244
export function fit(name: string, fn: FunctionWithParamTokens | AnyTestFn,
163245
timeOut: number = null): void {
164246
return _it(jsmIIt, name, fn, timeOut);

0 commit comments

Comments
 (0)
X Tutup