Skip to content

Commit 848cfee

Browse files
vikermanrkirov
authored andcommitted
feat(test): Implement fakeAsync using the FakeAsyncTestZoneSpec from zone.js.
Update the version of zone.js to @0.6.12 that contains the new FakeAsyncTestZoneSpec. The new fakeAsync zone handles errors better and clearPendingTimers() is no longer required to be called after handling an error and is deprecated. The fakeAsync test zone will now throw an error if an XHR is attemtped within the test since that cannot be controlled synchronously in the test(Need to be mocked out with a service implementation that doesn't involve XHRs). This commit also allows fakeAsync to wrap inject to make it consistent with async test zone. BREAKING CHANGE: inject can no longer wrap fakeAsync while fakeAsync can wrap inject. So the order in existing tests with inject and fakeAsync has to be switched as follows: Before: ``` inject([...], fakeAsync((...) => {...})) ``` After: ``` fakeAsync(inject([...], (...) => {...})) ``` Closes angular#8142
1 parent aeca0f0 commit 848cfee

File tree

15 files changed

+461
-533
lines changed

15 files changed

+461
-533
lines changed

karma-js.conf.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ module.exports = function(config) {
2121
'node_modules/zone.js/dist/long-stack-trace-zone.js',
2222
'node_modules/zone.js/dist/jasmine-patch.js',
2323
'node_modules/zone.js/dist/async-test.js',
24+
'node_modules/zone.js/dist/fake-async-test.js',
2425

2526
// Including systemjs because it defines `__eval`, which produces correct stack traces.
2627
'modules/angular2/src/testing/shims_for_IE.js',

modules/angular2/src/testing/fake_async.dart

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import 'dart:async' show runZoned, ZoneSpecification;
44
import 'package:quiver/testing/async.dart' as quiver;
55
import 'package:angular2/src/facade/exceptions.dart' show BaseException;
66

7+
import 'test_injector.dart' show getTestInjector, FunctionWithParamTokens;
8+
79
const _u = const Object();
810

911
quiver.FakeAsync _fakeAsync = null;
@@ -16,24 +18,38 @@ quiver.FakeAsync _fakeAsync = null;
1618
* If there are any pending timers at the end of the function, an exception
1719
* will be thrown.
1820
*
21+
* Can be used to wrap inject() calls.
22+
*
1923
* Returns a `Function` that wraps [fn].
2024
*/
21-
Function fakeAsync(Function fn) {
25+
Function fakeAsync(dynamic /* Function | FunctionWithParamTokens */ fn) {
2226
if (_fakeAsync != null) {
2327
throw 'fakeAsync() calls can not be nested';
2428
}
2529

26-
return (
27-
[a0 = _u,
28-
a1 = _u,
29-
a2 = _u,
30-
a3 = _u,
31-
a4 = _u,
32-
a5 = _u,
33-
a6 = _u,
34-
a7 = _u,
35-
a8 = _u,
36-
a9 = _u]) {
30+
Function innerFn = null;
31+
if (fn is FunctionWithParamTokens) {
32+
if (fn.isAsync) {
33+
throw 'Cannot wrap async test with fakeAsync';
34+
}
35+
innerFn = () { getTestInjector().execute(fn); };
36+
} else if (fn is Function) {
37+
innerFn = fn;
38+
} else {
39+
throw 'fakeAsync can wrap only test functions but got object of type ' +
40+
fn.runtimeType.toString();
41+
}
42+
43+
return ([a0 = _u,
44+
a1 = _u,
45+
a2 = _u,
46+
a3 = _u,
47+
a4 = _u,
48+
a5 = _u,
49+
a6 = _u,
50+
a7 = _u,
51+
a8 = _u,
52+
a9 = _u]) {
3753
// runZoned() to install a custom exception handler that re-throws
3854
return runZoned(() {
3955
return new quiver.FakeAsync().run((quiver.FakeAsync async) {
@@ -42,7 +58,7 @@ Function fakeAsync(Function fn) {
4258
List args = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]
4359
.takeWhile((a) => a != _u)
4460
.toList();
45-
var res = Function.apply(fn, args);
61+
var res = Function.apply(innerFn, args);
4662
_fakeAsync.flushMicrotasks();
4763

4864
if (async.periodicTimerCount > 0) {
Lines changed: 40 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,7 @@
1-
import {global} from 'angular2/src/facade/lang';
21
import {BaseException} from 'angular2/src/facade/exceptions';
3-
import {ListWrapper} from 'angular2/src/facade/collection';
2+
import {getTestInjector, FunctionWithParamTokens} from './test_injector';
43

5-
var _scheduler;
6-
var _microtasks: Function[] = [];
7-
var _pendingPeriodicTimers: number[] = [];
8-
var _pendingTimers: number[] = [];
9-
10-
class FakeAsyncZoneSpec implements ZoneSpec {
11-
static assertInZone(): void {
12-
if (!Zone.current.get('inFakeAsyncZone')) {
13-
throw new Error('The code should be running in the fakeAsync zone to call this function');
14-
}
15-
}
16-
17-
name: string = 'fakeAsync';
18-
19-
properties: {[key: string]: any} = {'inFakeAsyncZone': true};
20-
21-
onScheduleTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): Task {
22-
switch (task.type) {
23-
case 'microTask':
24-
_microtasks.push(task.invoke);
25-
break;
26-
case 'macroTask':
27-
switch (task.source) {
28-
case 'setTimeout':
29-
task.data['handleId'] = _setTimeout(task.invoke, task.data['delay'], task.data['args']);
30-
break;
31-
case 'setInterval':
32-
task.data['handleId'] =
33-
_setInterval(task.invoke, task.data['delay'], task.data['args']);
34-
break;
35-
default:
36-
task = delegate.scheduleTask(target, task);
37-
}
38-
break;
39-
case 'eventTask':
40-
task = delegate.scheduleTask(target, task);
41-
break;
42-
}
43-
return task;
44-
}
45-
46-
onCancelTask(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): any {
47-
switch (task.source) {
48-
case 'setTimeout':
49-
return _clearTimeout(task.data['handleId']);
50-
case 'setInterval':
51-
return _clearInterval(task.data['handleId']);
52-
default:
53-
return delegate.scheduleTask(target, task);
54-
}
55-
}
56-
}
4+
let _FakeAsyncTestZoneSpecType = Zone['FakeAsyncTestZoneSpec'];
575

586
/**
597
* Wraps a function to be executed in the fakeAsync zone:
@@ -62,64 +10,72 @@ class FakeAsyncZoneSpec implements ZoneSpec {
6210
*
6311
* If there are any pending timers at the end of the function, an exception will be thrown.
6412
*
13+
* Can be used to wrap inject() calls.
14+
*
6515
* ## Example
6616
*
6717
* {@example testing/ts/fake_async.ts region='basic'}
6818
*
6919
* @param fn
7020
* @returns {Function} The function wrapped to be executed in the fakeAsync zone
7121
*/
72-
export function fakeAsync(fn: Function): Function {
73-
if (Zone.current.get('inFakeAsyncZone')) {
74-
throw new Error('fakeAsync() calls can not be nested');
22+
export function fakeAsync(fn: Function | FunctionWithParamTokens): Function {
23+
if (Zone.current.get('FakeAsyncTestZoneSpec') != null) {
24+
throw new BaseException('fakeAsync() calls can not be nested');
7525
}
7626

77-
var fakeAsyncZone = Zone.current.fork(new FakeAsyncZoneSpec());
27+
let fakeAsyncTestZoneSpec = new _FakeAsyncTestZoneSpecType();
28+
let fakeAsyncZone = Zone.current.fork(fakeAsyncTestZoneSpec);
7829

79-
return function(...args) {
80-
// TODO(tbosch): This class should already be part of the jasmine typings but it is not...
81-
_scheduler = new (<any>jasmine).DelayedFunctionScheduler();
82-
clearPendingTimers();
30+
let innerTestFn: Function = null;
31+
32+
if (fn instanceof FunctionWithParamTokens) {
33+
if (fn.isAsync) {
34+
throw new BaseException('Cannot wrap async test with fakeAsync');
35+
}
36+
innerTestFn = () => { getTestInjector().execute(fn as FunctionWithParamTokens); };
37+
} else {
38+
innerTestFn = fn;
39+
}
8340

41+
return function(...args) {
8442
let res = fakeAsyncZone.run(() => {
85-
let res = fn(...args);
43+
let res = innerTestFn(...args);
8644
flushMicrotasks();
8745
return res;
8846
});
8947

90-
if (_pendingPeriodicTimers.length > 0) {
91-
throw new BaseException(
92-
`${_pendingPeriodicTimers.length} periodic timer(s) still in the queue.`);
48+
if (fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) {
49+
throw new BaseException(`${fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` +
50+
`periodic timer(s) still in the queue.`);
9351
}
9452

95-
if (_pendingTimers.length > 0) {
96-
throw new BaseException(`${_pendingTimers.length} timer(s) still in the queue.`);
53+
if (fakeAsyncTestZoneSpec.pendingTimers.length > 0) {
54+
throw new BaseException(
55+
`${fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`);
9756
}
98-
99-
_scheduler = null;
100-
ListWrapper.clear(_microtasks);
101-
10257
return res;
58+
};
59+
}
60+
61+
function _getFakeAsyncZoneSpec(): any {
62+
let zoneSpec = Zone.current.get('FakeAsyncTestZoneSpec');
63+
if (zoneSpec == null) {
64+
throw new Error('The code should be running in the fakeAsync zone to call this function');
10365
}
66+
return zoneSpec;
10467
}
10568

10669
/**
10770
* Clear the queue of pending timers and microtasks.
71+
* Tests no longer need to call this explicitly.
10872
*
109-
* Useful for cleaning up after an asynchronous test passes.
110-
*
111-
* ## Example
112-
*
113-
* {@example testing/ts/fake_async.ts region='pending'}
73+
* @deprecated
11474
*/
11575
export function clearPendingTimers(): void {
116-
// TODO we should fix tick to dequeue the failed timer instead of relying on clearPendingTimers
117-
ListWrapper.clear(_microtasks);
118-
ListWrapper.clear(_pendingPeriodicTimers);
119-
ListWrapper.clear(_pendingTimers);
76+
// Do nothing.
12077
}
12178

122-
12379
/**
12480
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
12581
*
@@ -133,54 +89,12 @@ export function clearPendingTimers(): void {
13389
* @param {number} millis Number of millisecond, defaults to 0
13490
*/
13591
export function tick(millis: number = 0): void {
136-
FakeAsyncZoneSpec.assertInZone();
137-
flushMicrotasks();
138-
_scheduler.tick(millis);
92+
_getFakeAsyncZoneSpec().tick(millis);
13993
}
14094

14195
/**
14296
* Flush any pending microtasks.
14397
*/
14498
export function flushMicrotasks(): void {
145-
FakeAsyncZoneSpec.assertInZone();
146-
while (_microtasks.length > 0) {
147-
var microtask = ListWrapper.removeAt(_microtasks, 0);
148-
microtask();
149-
}
150-
}
151-
152-
function _setTimeout(fn: Function, delay: number, args: any[]): number {
153-
var cb = _fnAndFlush(fn);
154-
var id = _scheduler.scheduleFunction(cb, delay, args);
155-
_pendingTimers.push(id);
156-
_scheduler.scheduleFunction(_dequeueTimer(id), delay);
157-
return id;
158-
}
159-
160-
function _clearTimeout(id: number) {
161-
_dequeueTimer(id);
162-
return _scheduler.removeFunctionWithId(id);
163-
}
164-
165-
function _setInterval(fn: Function, interval: number, ...args) {
166-
var cb = _fnAndFlush(fn);
167-
var id = _scheduler.scheduleFunction(cb, interval, args, true);
168-
_pendingPeriodicTimers.push(id);
169-
return id;
170-
}
171-
172-
function _clearInterval(id: number) {
173-
ListWrapper.remove(_pendingPeriodicTimers, id);
174-
return _scheduler.removeFunctionWithId(id);
175-
}
176-
177-
function _fnAndFlush(fn: Function): Function {
178-
return (...args) => {
179-
fn.apply(global, args);
180-
flushMicrotasks();
181-
}
182-
}
183-
184-
function _dequeueTimer(id: number): Function {
185-
return function() { ListWrapper.remove(_pendingTimers, id); }
99+
_getFakeAsyncZoneSpec().flushMicrotasks();
186100
}

modules/angular2/src/testing/test_injector.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,15 +195,15 @@ function emptyArray(): Array<any> {
195195
}
196196

197197
export class FunctionWithParamTokens {
198-
constructor(private _tokens: any[], private _fn: Function, public isAsync: boolean,
198+
constructor(private _tokens: any[], public fn: Function, public isAsync: boolean,
199199
public additionalProviders: () => any = emptyArray) {}
200200

201201
/**
202202
* Returns the value of the executed function.
203203
*/
204204
execute(injector: Injector): any {
205205
var params = this._tokens.map(t => injector.get(t));
206-
return FunctionWrapper.apply(this._fn, params);
206+
return FunctionWrapper.apply(this.fn, params);
207207
}
208208

209209
hasToken(token: any): boolean { return this._tokens.indexOf(token) > -1; }

modules/angular2/src/testing/testing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export type AsyncTestFn = (done: () => void) => void;
8787
/**
8888
* Signature for any simple testing function.
8989
*/
90-
export type AnyTestFn = SyncTestFn | AsyncTestFn;
90+
export type AnyTestFn = SyncTestFn | AsyncTestFn | Function;
9191

9292
var jsmBeforeEach = _global.beforeEach;
9393
var jsmIt = _global.it;

0 commit comments

Comments
 (0)