From 16cc9b46aa166b19620979bac19d82d025a2ac0c Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Tue, 2 Aug 2016 04:45:15 -0700 Subject: [PATCH] fix(fake_async): share zone between `beforeEach` and `it` This is needed for the case if a `beforeEach` instantiates `NgZone`and the `it` uses `TestComponentBuilder.createFakeAsync`. Otherwise the `NgZone` will use the root zone as parent, and `TestComponentBuilder.createFakeAsync` will always return undefined as `tick` does not forward promises created under the zone of `NgZone`. --- modules/@angular/core/test/fake_async_spec.ts | 16 ++++ modules/@angular/core/testing/fake_async.ts | 75 ++++++++++++------- modules/@angular/core/testing/testing.ts | 8 +- 3 files changed, 72 insertions(+), 27 deletions(-) diff --git a/modules/@angular/core/test/fake_async_spec.ts b/modules/@angular/core/test/fake_async_spec.ts index 09b3231766..ff78486433 100644 --- a/modules/@angular/core/test/fake_async_spec.ts +++ b/modules/@angular/core/test/fake_async_spec.ts @@ -285,5 +285,21 @@ export function main() { }).toThrowError('The code should be running in the fakeAsync zone to call this function'); }); }); + + describe('only one `fakeAsync` zone per test', () => { + let zoneInBeforeEach: Zone; + let zoneInTest1: Zone; + beforeEach(fakeAsync(() => { zoneInBeforeEach = Zone.current; })); + + it('should use the same zone as in beforeEach', fakeAsync(() => { + zoneInTest1 = Zone.current; + expect(zoneInTest1).toBe(zoneInBeforeEach); + })); + + it('should use a different zone between tests', fakeAsync(() => { + expect(Zone.current).toBe(zoneInBeforeEach); + expect(Zone.current).not.toBe(zoneInTest1); + })); + }); }); } diff --git a/modules/@angular/core/testing/fake_async.ts b/modules/@angular/core/testing/fake_async.ts index 1ab8aa29fe..235285c782 100644 --- a/modules/@angular/core/testing/fake_async.ts +++ b/modules/@angular/core/testing/fake_async.ts @@ -10,6 +10,22 @@ import {BaseException} from '../index'; let _FakeAsyncTestZoneSpecType = (Zone as any /** TODO #9100 */)['FakeAsyncTestZoneSpec']; +let _fakeAsyncZone: Zone = null; +let _fakeAsyncTestZoneSpec: any = null; + +/** + * Clears out the shared fake async zone for a test. + * To be called in a global `beforeEach`. + * + * @experimental + */ +export function resetFakeAsyncZone() { + _fakeAsyncZone = null; + _fakeAsyncTestZoneSpec = null; +} + +let _inFakeAsyncCall = false; + /** * Wraps a function to be executed in the fakeAsync zone: * - microtasks are manually executed by calling `flushMicrotasks()`, @@ -29,40 +45,49 @@ let _FakeAsyncTestZoneSpecType = (Zone as any /** TODO #9100 */)['FakeAsyncTestZ * @experimental */ export function fakeAsync(fn: Function): (...args: any[]) => any { - if (Zone.current.get('FakeAsyncTestZoneSpec') != null) { - throw new BaseException('fakeAsync() calls can not be nested'); - } - - let fakeAsyncTestZoneSpec = new _FakeAsyncTestZoneSpecType(); - let fakeAsyncZone = Zone.current.fork(fakeAsyncTestZoneSpec); - return function(...args: any[] /** TODO #9100 */) { - let res = fakeAsyncZone.run(() => { - let res = fn(...args); - flushMicrotasks(); + if (_inFakeAsyncCall) { + throw new BaseException('fakeAsync() calls can not be nested'); + } + _inFakeAsyncCall = true; + try { + if (!_fakeAsyncZone) { + if (Zone.current.get('FakeAsyncTestZoneSpec') != null) { + throw new BaseException('fakeAsync() calls can not be nested'); + } + + _fakeAsyncTestZoneSpec = new _FakeAsyncTestZoneSpecType(); + _fakeAsyncZone = Zone.current.fork(_fakeAsyncTestZoneSpec); + } + + let res = _fakeAsyncZone.run(() => { + let res = fn(...args); + flushMicrotasks(); + return res; + }); + + if (_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) { + throw new BaseException( + `${_fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` + + `periodic timer(s) still in the queue.`); + } + + if (_fakeAsyncTestZoneSpec.pendingTimers.length > 0) { + throw new BaseException( + `${_fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`); + } return res; - }); - - if (fakeAsyncTestZoneSpec.pendingPeriodicTimers.length > 0) { - throw new BaseException( - `${fakeAsyncTestZoneSpec.pendingPeriodicTimers.length} ` + - `periodic timer(s) still in the queue.`); + } finally { + _inFakeAsyncCall = false; } - - if (fakeAsyncTestZoneSpec.pendingTimers.length > 0) { - throw new BaseException( - `${fakeAsyncTestZoneSpec.pendingTimers.length} timer(s) still in the queue.`); - } - return res; }; } function _getFakeAsyncZoneSpec(): any { - let zoneSpec = Zone.current.get('FakeAsyncTestZoneSpec'); - if (zoneSpec == null) { + if (_fakeAsyncTestZoneSpec == null) { throw new Error('The code should be running in the fakeAsync zone to call this function'); } - return zoneSpec; + return _fakeAsyncTestZoneSpec; } /** diff --git a/modules/@angular/core/testing/testing.ts b/modules/@angular/core/testing/testing.ts index 3c3cf0631a..0c81b06ad2 100644 --- a/modules/@angular/core/testing/testing.ts +++ b/modules/@angular/core/testing/testing.ts @@ -14,15 +14,19 @@ import {SchemaMetadata} from '../index'; +import {resetFakeAsyncZone} from './fake_async'; import {TestBed, TestModuleMetadata, getTestBed} from './test_bed'; declare var global: any; var _global = (typeof window === 'undefined' ? global : window); -// Reset the test providers before each test. +// Reset the test providers and the fake async zone before each test. if (_global.beforeEach) { - _global.beforeEach(() => { TestBed.resetTestingModule(); }); + _global.beforeEach(() => { + TestBed.resetTestingModule(); + resetFakeAsyncZone(); + }); } /**