fix(testing): add an explicit doAsyncPrecompilation step (#10015)
This removes the magic from the `inject` test helper that would inspect the current zone and would only work with our `async` test helper. Now, `inject` is always synchronous, and if you are using a module that requires async precompilation, you're required to call `doAsyncPrecompilation` in your tests. This is part of the breaking changes introduced with the swap to each test having an AppModule. Closes #9975 Closes #9593 BREAKING CHANGE: `TestInjector` is now renamed to `TestBed` Before: ```js import {TestInjector, getTestInjector} from '@angular/core/testing'; ``` After: ```js import {TestBed, getTestBed} from '@angular/core/testing'; ```
This commit is contained in:
@ -18,11 +18,12 @@ const UNDEFINED = new Object();
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export class TestInjector implements Injector {
|
||||
export class TestBed implements Injector {
|
||||
private _instantiated: boolean = false;
|
||||
|
||||
private _compiler: Compiler = null;
|
||||
private _moduleRef: AppModuleRef<any> = null;
|
||||
private _appModuleFactory: AppModuleFactory<any> = null;
|
||||
|
||||
private _compilerProviders: Array<Type|Provider|any[]|any> = [];
|
||||
private _compilerUseJit: boolean = true;
|
||||
@ -36,6 +37,7 @@ export class TestInjector implements Injector {
|
||||
reset() {
|
||||
this._compiler = null;
|
||||
this._moduleRef = null;
|
||||
this._appModuleFactory = null;
|
||||
this._compilerProviders = [];
|
||||
this._compilerUseJit = true;
|
||||
this._providers = [];
|
||||
@ -89,16 +91,43 @@ export class TestInjector implements Injector {
|
||||
}
|
||||
}
|
||||
|
||||
createInjectorSync(): Injector {
|
||||
createAppModuleFactory(): Promise<AppModuleFactory<any>> {
|
||||
if (this._instantiated) {
|
||||
return this;
|
||||
throw new BaseException(
|
||||
'Cannot run precompilation when the test AppModule has already been instantiated. ' +
|
||||
'Make sure you are not using `inject` before `doAsyncPrecompilation`.');
|
||||
}
|
||||
|
||||
if (this._appModuleFactory) {
|
||||
return Promise.resolve(this._appModuleFactory);
|
||||
}
|
||||
|
||||
let moduleMeta = this._createCompilerAndModuleMeta();
|
||||
return this._createFromModuleFactory(
|
||||
this._compiler.compileAppModuleSync(_NoopModule, moduleMeta));
|
||||
|
||||
return this._compiler.compileAppModuleAsync(_NoopModule, moduleMeta)
|
||||
.then((appModuleFactory) => {
|
||||
this._appModuleFactory = appModuleFactory;
|
||||
return appModuleFactory;
|
||||
});
|
||||
}
|
||||
|
||||
createInjectorAsync(): Promise<Injector> {
|
||||
initTestAppModule() {
|
||||
if (this._instantiated) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._appModuleFactory) {
|
||||
this._createFromModuleFactory(this._appModuleFactory);
|
||||
} else {
|
||||
let moduleMeta = this._createCompilerAndModuleMeta();
|
||||
this._createFromModuleFactory(this._compiler.compileAppModuleSync(_NoopModule, moduleMeta));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_createInjectorAsync(): Promise<Injector> {
|
||||
if (this._instantiated) {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
@ -115,7 +144,7 @@ export class TestInjector implements Injector {
|
||||
deprecatedAppProviders: this._providers
|
||||
});
|
||||
const moduleMeta = new AppModuleMetadata({
|
||||
providers: this._providers.concat([{provide: TestInjector, useValue: this}]),
|
||||
providers: this._providers.concat([{provide: TestBed, useValue: this}]),
|
||||
modules: this._modules.concat([this.appModule]),
|
||||
directives: this._directives,
|
||||
pipes: this._pipes,
|
||||
@ -134,9 +163,9 @@ export class TestInjector implements Injector {
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) {
|
||||
if (!this._instantiated) {
|
||||
throw new BaseException(
|
||||
'Illegal state: The TestInjector has not yet been created. Call createInjectorSync/Async first!');
|
||||
'Illegal state: The test bed\'s injector has not yet been created. Call initTestAppModule first!');
|
||||
}
|
||||
if (token === TestInjector) {
|
||||
if (token === TestBed) {
|
||||
return this;
|
||||
}
|
||||
// Tests can inject things from the app module and from the compiler,
|
||||
@ -148,23 +177,30 @@ export class TestInjector implements Injector {
|
||||
execute(tokens: any[], fn: Function): any {
|
||||
if (!this._instantiated) {
|
||||
throw new BaseException(
|
||||
'Illegal state: The TestInjector has not yet been created. Call createInjectorSync/Async first!');
|
||||
'Illegal state: The test bed\'s injector has not yet been created. Call initTestAppModule first!');
|
||||
}
|
||||
var params = tokens.map(t => this.get(t));
|
||||
return FunctionWrapper.apply(fn, params);
|
||||
}
|
||||
}
|
||||
|
||||
var _testInjector: TestInjector = null;
|
||||
var _testBed: TestBed = null;
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export function getTestInjector() {
|
||||
if (_testInjector == null) {
|
||||
_testInjector = new TestInjector();
|
||||
export function getTestBed() {
|
||||
if (_testBed == null) {
|
||||
_testBed = new TestBed();
|
||||
}
|
||||
return _testInjector;
|
||||
return _testBed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getTestBed instead.
|
||||
*/
|
||||
export function getTestInjector() {
|
||||
return getTestBed();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,12 +244,12 @@ export function setBaseTestProviders(
|
||||
* @experimental
|
||||
*/
|
||||
export function initTestEnvironment(appModule: Type, platform: PlatformRef) {
|
||||
var testInjector = getTestInjector();
|
||||
if (testInjector.platform || testInjector.appModule) {
|
||||
var testBed = getTestBed();
|
||||
if (testBed.platform || testBed.appModule) {
|
||||
throw new BaseException('Cannot set base providers because it has already been called');
|
||||
}
|
||||
testInjector.platform = platform;
|
||||
testInjector.appModule = appModule;
|
||||
testBed.platform = platform;
|
||||
testBed.appModule = appModule;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,10 +267,22 @@ export function resetBaseTestProviders() {
|
||||
* @experimental
|
||||
*/
|
||||
export function resetTestEnvironment() {
|
||||
var testInjector = getTestInjector();
|
||||
testInjector.platform = null;
|
||||
testInjector.appModule = null;
|
||||
testInjector.reset();
|
||||
var testBed = getTestBed();
|
||||
testBed.platform = null;
|
||||
testBed.appModule = null;
|
||||
testBed.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run asynchronous precompilation for the test's AppModule. It is necessary to call this function
|
||||
* if your test is using an AppModule which has precompiled components that require an asynchronous
|
||||
* call, such as an XHR. Should be called once before the test case.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function doAsyncPrecompilation(): Promise<any> {
|
||||
let testBed = getTestBed();
|
||||
return testBed.createAppModuleFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -262,39 +310,31 @@ export function resetTestEnvironment() {
|
||||
* @stable
|
||||
*/
|
||||
export function inject(tokens: any[], fn: Function): () => any {
|
||||
let testInjector = getTestInjector();
|
||||
let testBed = getTestBed();
|
||||
if (tokens.indexOf(AsyncTestCompleter) >= 0) {
|
||||
return () => {
|
||||
// Return an async test method that returns a Promise if AsyncTestCompleter is one of the
|
||||
// injected tokens.
|
||||
return testInjector.createInjectorAsync().then(() => {
|
||||
let completer: AsyncTestCompleter = testInjector.get(AsyncTestCompleter);
|
||||
testInjector.execute(tokens, fn);
|
||||
return testBed._createInjectorAsync().then(() => {
|
||||
let completer: AsyncTestCompleter = testBed.get(AsyncTestCompleter);
|
||||
testBed.execute(tokens, fn);
|
||||
return completer.promise;
|
||||
});
|
||||
};
|
||||
} else {
|
||||
return () => {
|
||||
// Return a asynchronous test method with the injected tokens.
|
||||
// TODO(tbosch): Right now, we can only detect the AsyncTestZoneSpec via its name.
|
||||
// (see https://github.com/angular/zone.js/issues/370)
|
||||
if (Zone.current.name.toLowerCase().indexOf('asynctestzone') >= 0) {
|
||||
return testInjector.createInjectorAsync().then(() => testInjector.execute(tokens, fn));
|
||||
} else {
|
||||
// Return a synchronous test method with the injected tokens.
|
||||
try {
|
||||
testInjector.createInjectorSync();
|
||||
} catch (e) {
|
||||
if (e instanceof ComponentStillLoadingError) {
|
||||
throw new Error(
|
||||
`This test module precompiles the component ${stringify(e.compType)} which is using a "templateUrl", but the test is synchronous. ` +
|
||||
`Please use the "async(...)" or "fakeAsync(...)" helper functions to make the test asynchronous.`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
testBed.initTestAppModule();
|
||||
} catch (e) {
|
||||
if (e instanceof ComponentStillLoadingError) {
|
||||
throw new Error(
|
||||
`This test module precompiles the component ${stringify(e.compType)} which is using a "templateUrl", but precompilation was never done. ` +
|
||||
`Please call "doAsyncPrecompilation" before "inject".`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
return testInjector.execute(tokens, fn);
|
||||
}
|
||||
return testBed.execute(tokens, fn);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -314,14 +354,14 @@ export class InjectSetupWrapper {
|
||||
private _addModule() {
|
||||
var moduleDef = this._moduleDef();
|
||||
if (moduleDef) {
|
||||
getTestInjector().configureModule(moduleDef);
|
||||
getTestBed().configureModule(moduleDef);
|
||||
}
|
||||
}
|
||||
|
||||
inject(tokens: any[], fn: Function): () => any {
|
||||
return () => {
|
||||
this._addModule();
|
||||
return inject_impl(tokens, fn)();
|
||||
return inject(tokens, fn)();
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -346,9 +386,4 @@ export function withModule(moduleDef: () => {
|
||||
return new InjectSetupWrapper(moduleDef);
|
||||
}
|
||||
|
||||
|
||||
// This is to ensure inject(Async) within InjectSetupWrapper doesn't call itself
|
||||
// when transpiled to Dart.
|
||||
var inject_impl = inject;
|
||||
|
||||
class _NoopModule {}
|
@ -12,17 +12,17 @@
|
||||
* allows tests to be asynchronous by either returning a promise or using a 'done' parameter.
|
||||
*/
|
||||
|
||||
import {TestInjector, getTestInjector} from './test_injector';
|
||||
import {TestBed, getTestBed} from './test_bed';
|
||||
|
||||
declare var global: any;
|
||||
|
||||
var _global = <any>(typeof window === 'undefined' ? global : window);
|
||||
|
||||
var testInjector: TestInjector = getTestInjector();
|
||||
var testBed: TestBed = getTestBed();
|
||||
|
||||
// Reset the test providers before each test.
|
||||
if (_global.beforeEach) {
|
||||
_global.beforeEach(() => { testInjector.reset(); });
|
||||
_global.beforeEach(() => { testBed.reset(); });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,7 +34,7 @@ if (_global.beforeEach) {
|
||||
export function addProviders(providers: Array<any>): void {
|
||||
if (!providers) return;
|
||||
try {
|
||||
testInjector.configureModule({providers: providers});
|
||||
testBed.configureModule({providers: providers});
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'addProviders can\'t be called after the injector has been already created for this test. ' +
|
||||
@ -58,7 +58,7 @@ export function configureModule(moduleDef: {
|
||||
}): void {
|
||||
if (!moduleDef) return;
|
||||
try {
|
||||
testInjector.configureModule(moduleDef);
|
||||
testBed.configureModule(moduleDef);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'configureModule can\'t be called after the injector has been already created for this test. ' +
|
||||
@ -76,7 +76,7 @@ export function configureModule(moduleDef: {
|
||||
export function configureCompiler(config: {providers?: any[], useJit?: boolean}): void {
|
||||
if (!config) return;
|
||||
try {
|
||||
testInjector.configureCompiler(config);
|
||||
testBed.configureCompiler(config);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'configureCompiler can\'t be called after the injector has been already created for this test. ' +
|
||||
|
@ -11,11 +11,11 @@ import {StringMapWrapper} from '../src/facade/collection';
|
||||
import {Math, global, isFunction, isPromise} from '../src/facade/lang';
|
||||
|
||||
import {AsyncTestCompleter} from './async_test_completer';
|
||||
import {getTestInjector, inject} from './test_injector';
|
||||
import {getTestBed, inject} from './test_bed';
|
||||
|
||||
export {AsyncTestCompleter} from './async_test_completer';
|
||||
export {MockAnimationPlayer} from './mock_animation_player';
|
||||
export {inject} from './test_injector';
|
||||
export {inject} from './test_bed';
|
||||
export * from './logger';
|
||||
export * from './ng_zone_mock';
|
||||
export * from './mock_application_ref';
|
||||
@ -40,7 +40,7 @@ var inIt = false;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
|
||||
var globalTimeOut = jasmine.DEFAULT_TIMEOUT_INTERVAL;
|
||||
|
||||
var testInjector = getTestInjector();
|
||||
var testBed = getTestBed();
|
||||
|
||||
/**
|
||||
* Mechanism to run `beforeEach()` functions of Angular tests.
|
||||
@ -61,7 +61,7 @@ class BeforeEachRunner {
|
||||
}
|
||||
|
||||
// Reset the test providers before each test
|
||||
jsmBeforeEach(() => { testInjector.reset(); });
|
||||
jsmBeforeEach(() => { testBed.reset(); });
|
||||
|
||||
function _describe(jsmFn: any /** TODO #9100 */, ...args: any[] /** TODO #9100 */) {
|
||||
var parentRunner = runnerStack.length === 0 ? null : runnerStack[runnerStack.length - 1];
|
||||
@ -110,7 +110,7 @@ export function beforeEachProviders(fn: any /** TODO #9100 */): void {
|
||||
jsmBeforeEach(() => {
|
||||
var providers = fn();
|
||||
if (!providers) return;
|
||||
testInjector.configureModule({providers: providers});
|
||||
testBed.configureModule({providers: providers});
|
||||
});
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ function _it(jsmFn: Function, name: string, testFn: Function, testTimeOut: numbe
|
||||
return new AsyncTestCompleter();
|
||||
}
|
||||
};
|
||||
testInjector.configureModule({providers: [completerProvider]});
|
||||
testBed.configureModule({providers: [completerProvider]});
|
||||
runner.run();
|
||||
|
||||
inIt = true;
|
||||
|
Reference in New Issue
Block a user