diff --git a/modules/angular2/src/testing/test_injector.ts b/modules/angular2/src/testing/test_injector.ts index 1fac9583c7..377c2c9bd6 100644 --- a/modules/angular2/src/testing/test_injector.ts +++ b/modules/angular2/src/testing/test_injector.ts @@ -23,7 +23,7 @@ import { defaultKeyValueDiffers, ChangeDetectorGenConfig } from 'angular2/src/core/change_detection/change_detection'; -import {BaseException, ExceptionHandler} from 'angular2/src/facade/exceptions'; +import {ExceptionHandler} from 'angular2/src/facade/exceptions'; import {PipeResolver} from 'angular2/src/core/linker/pipe_resolver'; import {XHR} from 'angular2/src/compiler/xhr'; @@ -131,56 +131,14 @@ function _runtimeCompilerBindings() { ]; } -/** - * Configures an injector suitable for testing. - */ -export class TestInjector { - private _instantiated: boolean = false; - - private _injector: Injector = null; - - private _providers: Array = []; - - reset() { - this._injector = null; - this._providers = []; - this._instantiated = false; - } - - addProviders(providers: Array) { - if (this._instantiated) { - throw new BaseException('Cannot add providers after test injector is instantiated'); - } - this._providers = ListWrapper.concat(this._providers, providers); - } - - createInjector() { - var rootInjector = Injector.resolveAndCreate(_getRootProviders()); - this._injector = rootInjector.resolveAndCreateChild(ListWrapper.concat( - ListWrapper.concat(_getAppBindings(), _runtimeCompilerBindings()), this._providers)); - this._instantiated = true; - return this._injector; - } - - execute(fn: FunctionWithParamTokens): any { - if (!this._instantiated) { - this.createInjector(); - } - return fn.execute(this._injector); - } +export function createTestInjector(providers: Array): Injector { + var rootInjector = Injector.resolveAndCreate(_getRootProviders()); + return rootInjector.resolveAndCreateChild(ListWrapper.concat(_getAppBindings(), providers)); } -var _testInjector: TestInjector = null; - -/** - * Retrieve the {@link TestInjector}, possibly creating one if it doesn't - * exist yet. - */ -export function getTestInjector() { - if (_testInjector == null) { - _testInjector = new TestInjector(); - } - return _testInjector; +export function createTestInjectorWithRuntimeCompiler( + providers: Array): Injector { + return createTestInjector(ListWrapper.concat(_runtimeCompilerBindings(), providers)); } /** @@ -216,17 +174,12 @@ export function inject(tokens: any[], fn: Function): FunctionWithParamTokens { } /** - * Use {@link inject} instead, which now supports both synchronous and asynchronous tests. - * - * @deprecated + * @deprecated Use inject instead, which now supports both synchronous and asynchronous tests. */ export function injectAsync(tokens: any[], fn: Function): FunctionWithParamTokens { return new FunctionWithParamTokens(tokens, fn, true); } -/** - * A testing function with parameters which will be injected. See {@link inject} for details. - */ export class FunctionWithParamTokens { constructor(private _tokens: any[], private _fn: Function, public isAsync: boolean) {} diff --git a/modules/angular2/src/testing/testing.ts b/modules/angular2/src/testing/testing.ts index 875016029f..f6f7f735ab 100644 --- a/modules/angular2/src/testing/testing.ts +++ b/modules/angular2/src/testing/testing.ts @@ -7,11 +7,10 @@ import {ListWrapper} from 'angular2/src/facade/collection'; import {bind} from 'angular2/core'; import { + createTestInjectorWithRuntimeCompiler, FunctionWithParamTokens, inject, - injectAsync, - TestInjector, - getTestInjector + injectAsync } from './test_injector'; export {inject, injectAsync} from './test_injector'; @@ -93,10 +92,14 @@ var jsmIt = _global.it; var jsmIIt = _global.fit; var jsmXIt = _global.xit; -var testInjector: TestInjector = getTestInjector(); +var testProviders; +var injector; // Reset the test providers before each test. -jsmBeforeEach(() => { testInjector.reset(); }); +jsmBeforeEach(() => { + testProviders = []; + injector = null; +}); /** * Allows overriding default providers of the test injector, @@ -112,9 +115,8 @@ export function beforeEachProviders(fn): void { jsmBeforeEach(() => { var providers = fn(); if (!providers) return; - try { - testInjector.addProviders(providers); - } catch (e) { + testProviders = [...testProviders, ...providers]; + if (injector !== null) { throw new Error('beforeEachProviders was called after the injector had ' + 'been used in a beforeEach or it block. This invalidates the ' + 'test injector'); @@ -186,7 +188,11 @@ function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | An if (testFn instanceof FunctionWithParamTokens) { jsmFn(name, (done) => { - var returnedTestValue = runInTestZone(() => testInjector.execute(testFn), done, done.fail); + if (!injector) { + injector = createTestInjectorWithRuntimeCompiler(testProviders); + } + + var returnedTestValue = runInTestZone(() => testFn.execute(injector), done, done.fail); if (_isPromiseLike(returnedTestValue)) { (>returnedTestValue).then(null, (err) => { done.fail(err); }); } @@ -215,7 +221,13 @@ export function beforeEach(fn: FunctionWithParamTokens | AnyTestFn): void { // The test case uses inject(). ie `beforeEach(inject([ClassA], (a) => { ... // }));` - jsmBeforeEach((done) => { runInTestZone(() => testInjector.execute(fn), done, done.fail); }); + jsmBeforeEach((done) => { + if (!injector) { + injector = createTestInjectorWithRuntimeCompiler(testProviders); + } + + runInTestZone(() => fn.execute(injector), done, done.fail); + }); } else { // The test case doesn't use inject(). ie `beforeEach((done) => { ... }));` if ((fn).length === 0) { diff --git a/modules/angular2/src/testing/testing_internal.dart b/modules/angular2/src/testing/testing_internal.dart index 821300b742..0bc0f6d5a2 100644 --- a/modules/angular2/src/testing/testing_internal.dart +++ b/modules/angular2/src/testing/testing_internal.dart @@ -21,23 +21,20 @@ import 'package:angular2/src/core/reflection/reflection.dart'; import 'package:angular2/src/core/reflection/reflection_capabilities.dart'; import 'package:angular2/src/core/di/provider.dart' show bind; +import 'package:angular2/src/core/di/injector.dart' show Injector; import 'package:angular2/src/facade/collection.dart' show StringMapWrapper; import 'test_injector.dart'; export 'test_injector.dart' show inject; -TestInjector _testInjector = getTestInjector(); +List _testBindings = []; +Injector _injector; bool _isCurrentTestAsync; -Future _currentTestFuture; bool _inIt = false; class AsyncTestCompleter { final _completer = new Completer(); - AsyncTestCompleter() { - _currentTestFuture = this.future; - } - void done() { _completer.complete(); } @@ -53,11 +50,10 @@ void testSetup() { // - Priority 1: create the test injector to be used in beforeEach() and it() gns.beforeEach(() { - _testInjector.reset(); - _currentTestFuture = null; + _testBindings.clear(); }, priority: 3); - var completerProvider = bind(AsyncTestCompleter).toFactory(() { + var completerBinding = bind(AsyncTestCompleter).toFactory(() { // Mark the test as async when an AsyncTestCompleter is injected in an it(), if (!_inIt) throw 'AsyncTestCompleter can only be injected in an "it()"'; _isCurrentTestAsync = true; @@ -66,14 +62,15 @@ void testSetup() { gns.beforeEach(() { _isCurrentTestAsync = false; - _testInjector.addProviders([completerProvider]); + _testBindings.add(completerBinding); + _injector = createTestInjectorWithRuntimeCompiler(_testBindings); }, priority: 1); } /** - * Allows overriding default providers defined in test_injector.js. + * Allows overriding default bindings defined in test_injector.js. * - * The given function must return a list of DI providers. + * The given function must return a list of DI bindings. * * Example: * @@ -84,8 +81,8 @@ void testSetup() { */ void beforeEachProviders(Function fn) { gns.beforeEach(() { - var providers = fn(); - if (providers != null) _testInjector.addProviders(providers); + var bindings = fn(); + if (bindings != null) _testBindings.addAll(bindings); }, priority: 2); } @@ -98,7 +95,7 @@ void beforeEach(fn) { if (fn is! FunctionWithParamTokens) fn = new FunctionWithParamTokens([], fn, false); gns.beforeEach(() { - _testInjector.execute(fn); + fn.execute(_injector); }); } @@ -107,9 +104,9 @@ void _it(gnsFn, name, fn) { new FunctionWithParamTokens([], fn, false); gnsFn(name, () { _inIt = true; - _testInjector.execute(fn); + fn.execute(_injector); _inIt = false; - if (_isCurrentTestAsync) return _currentTestFuture; + if (_isCurrentTestAsync) return _injector.get(AsyncTestCompleter).future; }); } diff --git a/modules/angular2/src/testing/testing_internal.ts b/modules/angular2/src/testing/testing_internal.ts index 72ca9ebb10..86ac1e380e 100644 --- a/modules/angular2/src/testing/testing_internal.ts +++ b/modules/angular2/src/testing/testing_internal.ts @@ -5,7 +5,11 @@ import {NgZoneZone} from 'angular2/src/core/zone/ng_zone'; import {provide} from 'angular2/core'; -import {TestInjector, getTestInjector, FunctionWithParamTokens, inject} from './test_injector'; +import { + createTestInjectorWithRuntimeCompiler, + FunctionWithParamTokens, + inject +} from './test_injector'; import {browserDetection} from './utils'; export {inject} from './test_injector'; @@ -44,7 +48,7 @@ var inIt = false; jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; var globalTimeOut = browserDetection.isSlow ? 3000 : jasmine.DEFAULT_TIMEOUT_INTERVAL; -var testInjector = getTestInjector(); +var testProviders; /** * Mechanism to run `beforeEach()` functions of Angular tests. @@ -58,17 +62,16 @@ class BeforeEachRunner { beforeEach(fn: FunctionWithParamTokens | SyncTestFn): void { this._fns.push(fn); } - run(): void { - if (this._parent) this._parent.run(); + run(injector): void { + if (this._parent) this._parent.run(injector); this._fns.forEach((fn) => { - return isFunction(fn) ? (fn)() : - (testInjector.execute(fn)); + return isFunction(fn) ? (fn)() : (fn).execute(injector); }); } } // Reset the test providers before each test -jsmBeforeEach(() => { testInjector.reset(); }); +jsmBeforeEach(() => { testProviders = []; }); function _describe(jsmFn, ...args) { var parentRunner = runnerStack.length === 0 ? null : runnerStack[runnerStack.length - 1]; @@ -117,7 +120,7 @@ export function beforeEachProviders(fn): void { jsmBeforeEach(() => { var providers = fn(); if (!providers) return; - testInjector.addProviders(providers); + testProviders = [...testProviders, ...providers]; }); } @@ -147,17 +150,18 @@ function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | An } }); - testInjector.addProviders([completerProvider]); - runner.run(); + var injector = createTestInjectorWithRuntimeCompiler([...testProviders, completerProvider]); + runner.run(injector); inIt = true; - testInjector.execute(testFn); + testFn.execute(injector); inIt = false; }, timeOut); } else { jsmFn(name, () => { - runner.run(); - testInjector.execute(testFn); + var injector = createTestInjectorWithRuntimeCompiler(testProviders); + runner.run(injector); + testFn.execute(injector); }, timeOut); } @@ -166,12 +170,14 @@ function _it(jsmFn: Function, name: string, testFn: FunctionWithParamTokens | An if ((testFn).length === 0) { jsmFn(name, () => { - runner.run(); + var injector = createTestInjectorWithRuntimeCompiler(testProviders); + runner.run(injector); (testFn)(); }, timeOut); } else { jsmFn(name, (done) => { - runner.run(); + var injector = createTestInjectorWithRuntimeCompiler(testProviders); + runner.run(injector); (testFn)(done); }, timeOut); } diff --git a/modules/angular2/test/web_workers/debug_tools/multi_client_server_message_bus.server.spec.dart b/modules/angular2/test/web_workers/debug_tools/multi_client_server_message_bus.server.spec.dart index 857b948b0b..43494c9e6a 100644 --- a/modules/angular2/test/web_workers/debug_tools/multi_client_server_message_bus.server.spec.dart +++ b/modules/angular2/test/web_workers/debug_tools/multi_client_server_message_bus.server.spec.dart @@ -11,6 +11,7 @@ import "package:angular2/testing_internal.dart" iit, expect, beforeEach, + createTestInjector, beforeEachProviders, SpyObject, proxy; diff --git a/modules/angular2/test/web_workers/debug_tools/single_client_server_message_bus.server.spec.dart b/modules/angular2/test/web_workers/debug_tools/single_client_server_message_bus.server.spec.dart index 50ca5f15ae..0cf86f781b 100644 --- a/modules/angular2/test/web_workers/debug_tools/single_client_server_message_bus.server.spec.dart +++ b/modules/angular2/test/web_workers/debug_tools/single_client_server_message_bus.server.spec.dart @@ -10,6 +10,7 @@ import "package:angular2/testing_internal.dart" it, expect, beforeEach, + createTestInjector, beforeEachProviders, SpyObject, proxy; diff --git a/modules/angular2/test/web_workers/debug_tools/web_socket_message_bus_spec.dart b/modules/angular2/test/web_workers/debug_tools/web_socket_message_bus_spec.dart index 9b87a3e281..6823eb008a 100644 --- a/modules/angular2/test/web_workers/debug_tools/web_socket_message_bus_spec.dart +++ b/modules/angular2/test/web_workers/debug_tools/web_socket_message_bus_spec.dart @@ -8,6 +8,7 @@ import "package:angular2/testing_internal.dart" it, expect, beforeEach, + createTestInjector, beforeEachProviders, SpyObject, proxy; diff --git a/modules/angular2/test/web_workers/shared/message_bus_spec.ts b/modules/angular2/test/web_workers/shared/message_bus_spec.ts index 1e3073c94e..e4fcc2ade0 100644 --- a/modules/angular2/test/web_workers/shared/message_bus_spec.ts +++ b/modules/angular2/test/web_workers/shared/message_bus_spec.ts @@ -5,6 +5,7 @@ import { it, expect, beforeEach, + createTestInjectorWithRuntimeCompiler, beforeEachProviders, SpyObject, proxy diff --git a/modules/angular2/test/web_workers/shared/service_message_broker_spec.ts b/modules/angular2/test/web_workers/shared/service_message_broker_spec.ts index d30be8bc59..9b0b148221 100644 --- a/modules/angular2/test/web_workers/shared/service_message_broker_spec.ts +++ b/modules/angular2/test/web_workers/shared/service_message_broker_spec.ts @@ -5,6 +5,7 @@ import { it, expect, beforeEach, + createTestInjectorWithRuntimeCompiler, beforeEachProviders, SpyObject, proxy diff --git a/modules/angular2/test/web_workers/worker/event_dispatcher_spec.ts b/modules/angular2/test/web_workers/worker/event_dispatcher_spec.ts index a97d1b0096..5efa1ea811 100644 --- a/modules/angular2/test/web_workers/worker/event_dispatcher_spec.ts +++ b/modules/angular2/test/web_workers/worker/event_dispatcher_spec.ts @@ -5,6 +5,7 @@ import { it, expect, beforeEach, + createTestInjectorWithRuntimeCompiler, beforeEachProviders, SpyObject, proxy diff --git a/modules/angular2/test/web_workers/worker/renderer_integration_spec.ts b/modules/angular2/test/web_workers/worker/renderer_integration_spec.ts index 8f56648945..dbeca90582 100644 --- a/modules/angular2/test/web_workers/worker/renderer_integration_spec.ts +++ b/modules/angular2/test/web_workers/worker/renderer_integration_spec.ts @@ -7,8 +7,8 @@ import { iit, expect, beforeEach, + createTestInjectorWithRuntimeCompiler, beforeEachProviders, - TestInjector, TestComponentBuilder } from "angular2/testing_internal"; import {DOM} from 'angular2/src/platform/dom/dom_adapter'; @@ -102,14 +102,12 @@ export function main() { beforeEachProviders(() => { var uiRenderProtoViewStore = new RenderProtoViewRefStore(false); uiRenderViewStore = new RenderViewWithFragmentsStore(false); - var testInjector = new TestInjector(); - testInjector.addProviders([ + uiInjector = createTestInjectorWithRuntimeCompiler([ provide(RenderProtoViewRefStore, {useValue: uiRenderProtoViewStore}), provide(RenderViewWithFragmentsStore, {useValue: uiRenderViewStore}), provide(DomRenderer, {useClass: DomRenderer_}), provide(Renderer, {useExisting: DomRenderer}) ]); - uiInjector = testInjector.createInjector(); var uiSerializer = uiInjector.get(Serializer); var domRenderer = uiInjector.get(DomRenderer); var workerRenderProtoViewStore = new RenderProtoViewRefStore(true); diff --git a/modules/angular2/test/web_workers/worker/xhr_impl_spec.ts b/modules/angular2/test/web_workers/worker/xhr_impl_spec.ts index dfb68279af..83fe0f9828 100644 --- a/modules/angular2/test/web_workers/worker/xhr_impl_spec.ts +++ b/modules/angular2/test/web_workers/worker/xhr_impl_spec.ts @@ -5,6 +5,7 @@ import { it, expect, beforeEach, + createTestInjectorWithRuntimeCompiler, beforeEachProviders } from 'angular2/testing_internal'; import {SpyMessageBroker} from './spies'; diff --git a/modules_dart/angular2_testing/lib/angular2_testing.dart b/modules_dart/angular2_testing/lib/angular2_testing.dart index da737b3863..60d1302dd3 100644 --- a/modules_dart/angular2_testing/lib/angular2_testing.dart +++ b/modules_dart/angular2_testing/lib/angular2_testing.dart @@ -1,8 +1,11 @@ library angular2_testing.angular2_testing; import 'package:test/test.dart'; +import 'package:test/src/backend/invoker.dart'; +import 'package:test/src/backend/live_test.dart'; import 'package:angular2/angular2.dart'; +import 'package:angular2/src/core/di/injector.dart' show Injector; import 'package:angular2/src/core/di/metadata.dart' show InjectMetadata; import 'package:angular2/src/core/di/exceptions.dart' show NoAnnotationError; import 'package:angular2/platform/browser_static.dart' show BrowserDomAdapter; @@ -28,13 +31,6 @@ void initAngularTests() { reflector.reflectionCapabilities = new ReflectionCapabilities(); } -void _addTestInjectorTearDown() { - // Multiple resets are harmless. - tearDown(() { - _testInjector.reset(); - }); -} - /// Allows overriding default bindings defined in test_injector.dart. /// /// The given function must return a list of DI providers. @@ -49,17 +45,13 @@ void _addTestInjectorTearDown() { /// ``` void setUpProviders(Iterable providerFactory()) { setUp(() { - try { - _testInjector.addProviders(providerFactory()); - } catch(e) { + if (_currentInjector != null) { throw 'setUpProviders was called after the injector had ' 'been used in a setUp or test block. This invalidates the ' 'test injector'; } - + _currentTestProviders.addAll(providerFactory()); }); - - _addTestInjectorTearDown(); } dynamic _runInjectableFunction(Function fn) { @@ -80,8 +72,11 @@ dynamic _runInjectableFunction(Function fn) { tokens.add(token); } + if (_currentInjector == null) { + _currentInjector = createTestInjectorWithRuntimeCompiler(_currentTestProviders); + } var injectFn = new FunctionWithParamTokens(tokens, fn, false); - return _testInjector.execute(injectFn); + return injectFn.execute(_currentInjector); } /// Use the test injector to get bindings and run a function. @@ -97,8 +92,6 @@ void ngSetUp(Function fn) { setUp(() async { await _runInjectableFunction(fn); }); - - _addTestInjectorTearDown(); } /// Add a test which can use the test injector. @@ -115,8 +108,25 @@ void ngTest(String description, Function fn, test(description, () async { await _runInjectableFunction(fn); }, testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform); - - _addTestInjectorTearDown(); } -final TestInjector _testInjector = getTestInjector(); +final _providersExpando = + new Expando>('Providers for the current test'); +final _injectorExpando = + new Expando('Angular Injector for the current test'); + +List get _currentTestProviders { + if (_providersExpando[_currentTest] == null) { + return _providersExpando[_currentTest] = []; + } + return _providersExpando[_currentTest]; +} + +Injector get _currentInjector => _injectorExpando[_currentTest]; +void set _currentInjector(Injector newInjector) { + _injectorExpando[_currentTest] = newInjector; +} + +// TODO: warning, the Invoker.current.liveTest is not a settled API and is +// subject to change in future versions of package:test. +LiveTest get _currentTest => Invoker.current.liveTest;