feat(testing): add implicit test module
Every test now has an implicit module. It can be configured via `configureModule` (from @angular/core/testing)
to add providers, directives, pipes, ...
The compiler now has to be configured separately via `configureCompiler` (from @angular/core/testing)
to add providers or define whether to use jit.
BREAKING CHANGE:
- Application providers can no longer inject compiler internals (i.e. everything
from `@angular/compiler). Inject `Compiler` instead. This reflects the
changes to `bootstrap` for module support (3f55aa609f
).
- Compiler providers can no longer be added via `addProviders` / `withProviders`.
Use the new method `configureCompiler` instead.
- Platform directives / pipes need to be provided via
`configureModule` and can no longer be provided via the
`PLATFORM_PIPES` / `PLATFORM_DIRECTIVES` tokens.
- `setBaseTestProviders()` was renamed into `initTestEnvironment` and
now takes a `PlatformRef` and a factory for a
`Compiler`.
- E.g. for the browser platform:
BEFORE:
```
import {setBaseTestProviders} from ‘@angular/core/testing’;
import {TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS} from ‘@angular/platform-browser-dynamic/testing’;
setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
```
AFTER:
```
import {setBaseTestProviders} from ‘@angular/core/testing’;
import {browserTestCompiler, browserDynamicTestPlatform,
BrowserDynamicTestModule} from ‘@angular/platform-browser-dynamic/testing’;
initTestEnvironment(
browserTestCompiler,
browserDynamicTestPlatform(),
BrowserDynamicTestModule);
```
- E.g. for the server platform:
BEFORE:
```
import {setBaseTestProviders} from ‘@angular/core/testing’;
import {TEST_SERVER_PLATFORM_PROVIDERS,
TEST_SERVER_APPLICATION_PROVIDERS} from ‘@angular/platform-server/testing/server’;
setBaseTestProviders(TEST_SERVER_PLATFORM_PROVIDERS,
TEST_SERVER_APPLICATION_PROVIDERS);
```
AFTER:
```
import {setBaseTestProviders} from ‘@angular/core/testing’;
import {serverTestCompiler, serverTestPlatform,
ServerTestModule} from ‘@angular/platform-browser-dynamic/testing’;
initTestEnvironment(
serverTestCompiler,
serverTestPlatform(),
ServerTestModule);
```
Related to #9726
Closes #9846
This commit is contained in:
@ -6,61 +6,157 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {PLATFORM_INITIALIZER, Provider, ReflectiveInjector, Type} from '../index';
|
||||
import {lockRunMode} from '../src/application_ref';
|
||||
import {AppModule, AppModuleFactory, AppModuleMetadata, AppModuleRef, Compiler, ComponentStillLoadingError, Injector, PlatformRef, Provider, Type} from '../index';
|
||||
import {ListWrapper} from '../src/facade/collection';
|
||||
import {BaseException} from '../src/facade/exceptions';
|
||||
import {FunctionWrapper, isPresent} from '../src/facade/lang';
|
||||
import {FunctionWrapper, isPresent, stringify} from '../src/facade/lang';
|
||||
|
||||
import {AsyncTestCompleter} from './async_test_completer';
|
||||
|
||||
const UNDEFINED = new Object();
|
||||
|
||||
/**
|
||||
* Signature of the compiler factory passed to `initTestEnvironment`.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export type TestCompilerFactory =
|
||||
(config: {providers?: Array<Type|Provider|any[]>, useJit?: boolean}) => Compiler;
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export class TestInjector {
|
||||
export class TestInjector implements Injector {
|
||||
private _instantiated: boolean = false;
|
||||
|
||||
private _injector: ReflectiveInjector = null;
|
||||
private _compiler: Compiler = null;
|
||||
private _moduleRef: AppModuleRef<any> = null;
|
||||
|
||||
private _compilerProviders: Array<Type|Provider|any[]|any> = [];
|
||||
private _compilerUseJit: boolean = true;
|
||||
|
||||
private _providers: Array<Type|Provider|any[]|any> = [];
|
||||
private _directives: Array<Type|any[]|any> = [];
|
||||
private _pipes: Array<Type|any[]|any> = [];
|
||||
private _modules: Array<Type|any[]|any> = [];
|
||||
private _precompile: Array<Type|any[]|any> = [];
|
||||
|
||||
reset() {
|
||||
this._injector = null;
|
||||
this._compiler = null;
|
||||
this._moduleRef = null;
|
||||
this._compilerProviders = [];
|
||||
this._compilerUseJit = true;
|
||||
this._providers = [];
|
||||
this._directives = [];
|
||||
this._pipes = [];
|
||||
this._modules = [];
|
||||
this._precompile = [];
|
||||
this._instantiated = false;
|
||||
}
|
||||
|
||||
platformProviders: Array<Type|Provider|any[]|any> = [];
|
||||
compilerFactory: TestCompilerFactory = null;
|
||||
|
||||
applicationProviders: Array<Type|Provider|any[]|any> = [];
|
||||
platform: PlatformRef = null;
|
||||
|
||||
addProviders(providers: Array<Type|Provider|any[]|any>) {
|
||||
appModule: Type = null;
|
||||
|
||||
configureCompiler(config: {providers?: any[], useJit?: boolean}) {
|
||||
if (this._instantiated) {
|
||||
throw new BaseException('Cannot add providers after test injector is instantiated');
|
||||
throw new BaseException('Cannot add configuration after test injector is instantiated');
|
||||
}
|
||||
if (config.providers) {
|
||||
this._compilerProviders = ListWrapper.concat(this._compilerProviders, config.providers);
|
||||
}
|
||||
if (config.useJit !== undefined) {
|
||||
this._compilerUseJit = config.useJit;
|
||||
}
|
||||
this._providers = ListWrapper.concat(this._providers, providers);
|
||||
}
|
||||
|
||||
createInjector() {
|
||||
lockRunMode();
|
||||
var rootInjector = ReflectiveInjector.resolveAndCreate(this.platformProviders);
|
||||
this._injector = rootInjector.resolveAndCreateChild(
|
||||
ListWrapper.concat(this.applicationProviders, this._providers));
|
||||
configureModule(moduleDef: {
|
||||
providers?: any[],
|
||||
directives?: any[],
|
||||
pipes?: any[],
|
||||
precompile?: any[],
|
||||
modules?: any[]
|
||||
}) {
|
||||
if (this._instantiated) {
|
||||
throw new BaseException('Cannot add configuration after test injector is instantiated');
|
||||
}
|
||||
if (moduleDef.providers) {
|
||||
this._providers = ListWrapper.concat(this._providers, moduleDef.providers);
|
||||
}
|
||||
if (moduleDef.directives) {
|
||||
this._directives = ListWrapper.concat(this._directives, moduleDef.directives);
|
||||
}
|
||||
if (moduleDef.pipes) {
|
||||
this._pipes = ListWrapper.concat(this._pipes, moduleDef.pipes);
|
||||
}
|
||||
if (moduleDef.precompile) {
|
||||
this._precompile = ListWrapper.concat(this._precompile, moduleDef.precompile);
|
||||
}
|
||||
if (moduleDef.modules) {
|
||||
this._modules = ListWrapper.concat(this._modules, moduleDef.modules);
|
||||
}
|
||||
}
|
||||
|
||||
createInjectorSync(): Injector {
|
||||
if (this._instantiated) {
|
||||
return this;
|
||||
}
|
||||
let moduleMeta = this._createCompilerAndModuleMeta();
|
||||
return this._createFromModuleFactory(
|
||||
this._compiler.compileAppModuleSync(_NoopModule, moduleMeta));
|
||||
}
|
||||
|
||||
createInjectorAsync(): Promise<Injector> {
|
||||
if (this._instantiated) {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
let moduleMeta = this._createCompilerAndModuleMeta();
|
||||
return this._compiler.compileAppModuleAsync(_NoopModule, moduleMeta)
|
||||
.then((appModuleFactory) => this._createFromModuleFactory(appModuleFactory));
|
||||
}
|
||||
|
||||
private _createCompilerAndModuleMeta(): AppModuleMetadata {
|
||||
this._compiler =
|
||||
this.compilerFactory({providers: this._compilerProviders, useJit: this._compilerUseJit});
|
||||
const moduleMeta = new AppModuleMetadata({
|
||||
providers: this._providers.concat([{provide: TestInjector, useValue: this}]),
|
||||
modules: this._modules.concat([this.appModule]),
|
||||
directives: this._directives,
|
||||
pipes: this._pipes,
|
||||
precompile: this._precompile
|
||||
});
|
||||
|
||||
return moduleMeta;
|
||||
}
|
||||
|
||||
private _createFromModuleFactory(appModuleFactory: AppModuleFactory<any>): Injector {
|
||||
this._moduleRef = appModuleFactory.create(this.platform.injector);
|
||||
this._instantiated = true;
|
||||
return this._injector;
|
||||
return this;
|
||||
}
|
||||
|
||||
get(token: any) {
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) {
|
||||
if (!this._instantiated) {
|
||||
this.createInjector();
|
||||
throw new BaseException(
|
||||
'Illegal state: The TestInjector has not yet been created. Call createInjectorSync/Async first!');
|
||||
}
|
||||
return this._injector.get(token);
|
||||
if (token === TestInjector) {
|
||||
return this;
|
||||
}
|
||||
// Tests can inject things from the app module and from the compiler,
|
||||
// but the app module can't inject things from the compiler and vice versa.
|
||||
let result = this._moduleRef.injector.get(token, UNDEFINED);
|
||||
return result === UNDEFINED ? this._compiler.injector.get(token, notFoundValue) : result;
|
||||
}
|
||||
|
||||
execute(tokens: any[], fn: Function): any {
|
||||
if (!this._instantiated) {
|
||||
this.createInjector();
|
||||
throw new BaseException(
|
||||
'Illegal state: The TestInjector has not yet been created. Call createInjectorSync/Async first!');
|
||||
}
|
||||
var params = tokens.map(t => this._injector.get(t));
|
||||
var params = tokens.map(t => this.get(t));
|
||||
return FunctionWrapper.apply(fn, params);
|
||||
}
|
||||
}
|
||||
@ -83,28 +179,22 @@ export function getTestInjector() {
|
||||
*
|
||||
* This may only be called once, to set up the common providers for the current test
|
||||
* suite on the current platform. If you absolutely need to change the providers,
|
||||
* first use `resetBaseTestProviders`.
|
||||
* first use `resetTestEnvironment`.
|
||||
*
|
||||
* Test Providers for individual platforms are available from
|
||||
* 'angular2/platform/testing/<platform_name>'.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function setBaseTestProviders(
|
||||
platformProviders: Array<Type|Provider|any[]>,
|
||||
applicationProviders: Array<Type|Provider|any[]>) {
|
||||
export function initTestEnvironment(
|
||||
compilerFactory: TestCompilerFactory, platform: PlatformRef, appModule: Type) {
|
||||
var testInjector = getTestInjector();
|
||||
if (testInjector.platformProviders.length > 0 || testInjector.applicationProviders.length > 0) {
|
||||
if (testInjector.compilerFactory || testInjector.platform || testInjector.appModule) {
|
||||
throw new BaseException('Cannot set base providers because it has already been called');
|
||||
}
|
||||
testInjector.platformProviders = platformProviders;
|
||||
testInjector.applicationProviders = applicationProviders;
|
||||
var injector = testInjector.createInjector();
|
||||
let inits: Function[] = injector.get(PLATFORM_INITIALIZER, null);
|
||||
if (isPresent(inits)) {
|
||||
inits.forEach(init => init());
|
||||
}
|
||||
testInjector.reset();
|
||||
testInjector.compilerFactory = compilerFactory;
|
||||
testInjector.platform = platform;
|
||||
testInjector.appModule = appModule;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,10 +202,11 @@ export function setBaseTestProviders(
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export function resetBaseTestProviders() {
|
||||
export function resetTestEnvironment() {
|
||||
var testInjector = getTestInjector();
|
||||
testInjector.platformProviders = [];
|
||||
testInjector.applicationProviders = [];
|
||||
testInjector.compilerFactory = null;
|
||||
testInjector.platform = null;
|
||||
testInjector.appModule = null;
|
||||
testInjector.reset();
|
||||
}
|
||||
|
||||
@ -146,16 +237,38 @@ export function resetBaseTestProviders() {
|
||||
export function inject(tokens: any[], fn: Function): () => any {
|
||||
let testInjector = getTestInjector();
|
||||
if (tokens.indexOf(AsyncTestCompleter) >= 0) {
|
||||
// Return an async test method that returns a Promise if AsyncTestCompleter is one of the
|
||||
// injected tokens.
|
||||
return () => {
|
||||
let completer: AsyncTestCompleter = testInjector.get(AsyncTestCompleter);
|
||||
testInjector.execute(tokens, fn);
|
||||
return completer.promise;
|
||||
// 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 completer.promise;
|
||||
});
|
||||
};
|
||||
} else {
|
||||
// Return a synchronous test method with the injected tokens.
|
||||
return () => { return getTestInjector().execute(tokens, fn); };
|
||||
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;
|
||||
}
|
||||
}
|
||||
return testInjector.execute(tokens, fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,18 +276,24 @@ export function inject(tokens: any[], fn: Function): () => any {
|
||||
* @experimental
|
||||
*/
|
||||
export class InjectSetupWrapper {
|
||||
constructor(private _providers: () => any) {}
|
||||
constructor(private _moduleDef: () => {
|
||||
providers?: any[],
|
||||
directives?: any[],
|
||||
pipes?: any[],
|
||||
precompile?: any[],
|
||||
modules?: any[]
|
||||
}) {}
|
||||
|
||||
private _addProviders() {
|
||||
var additionalProviders = this._providers();
|
||||
if (additionalProviders.length > 0) {
|
||||
getTestInjector().addProviders(additionalProviders);
|
||||
private _addModule() {
|
||||
var moduleDef = this._moduleDef();
|
||||
if (moduleDef) {
|
||||
getTestInjector().configureModule(moduleDef);
|
||||
}
|
||||
}
|
||||
|
||||
inject(tokens: any[], fn: Function): () => any {
|
||||
return () => {
|
||||
this._addProviders();
|
||||
this._addModule();
|
||||
return inject_impl(tokens, fn)();
|
||||
};
|
||||
}
|
||||
@ -184,9 +303,25 @@ export class InjectSetupWrapper {
|
||||
* @experimental
|
||||
*/
|
||||
export function withProviders(providers: () => any) {
|
||||
return new InjectSetupWrapper(providers);
|
||||
return new InjectSetupWrapper(() => {{return {providers: providers()};}});
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export function withModule(moduleDef: () => {
|
||||
providers?: any[],
|
||||
directives?: any[],
|
||||
pipes?: any[],
|
||||
precompile?: any[],
|
||||
modules?: any[]
|
||||
}) {
|
||||
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 {}
|
||||
|
@ -123,7 +123,7 @@ if (_global.beforeEach) {
|
||||
export function addProviders(providers: Array<any>): void {
|
||||
if (!providers) return;
|
||||
try {
|
||||
testInjector.addProviders(providers);
|
||||
testInjector.configureModule({providers: providers});
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'addProviders can\'t be called after the injector has been already created for this test. ' +
|
||||
@ -132,6 +132,48 @@ export function addProviders(providers: Array<any>): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows overriding default providers, directives, pipes, modules of the test injector,
|
||||
* which are defined in test_injector.js
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export function configureModule(moduleDef: {
|
||||
providers?: any[],
|
||||
directives?: any[],
|
||||
pipes?: any[],
|
||||
precompile?: any[],
|
||||
modules?: any[]
|
||||
}): void {
|
||||
if (!moduleDef) return;
|
||||
try {
|
||||
testInjector.configureModule(moduleDef);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'configureModule can\'t be called after the injector has been already created for this test. ' +
|
||||
'This is most likely because you\'ve already used the injector to inject a beforeEach or the ' +
|
||||
'current `it` function.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows overriding default compiler providers and settings
|
||||
* which are defined in test_injector.js
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export function configureCompiler(config: {providers?: any[], useJit?: boolean}): void {
|
||||
if (!config) return;
|
||||
try {
|
||||
testInjector.configureCompiler(config);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'configureCompiler can\'t be called after the injector has been already created for this test. ' +
|
||||
'This is most likely because you\'ve already used the injector to inject a beforeEach or the ' +
|
||||
'current `it` function.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use beforeEach(() => addProviders())
|
||||
*/
|
||||
|
@ -110,7 +110,7 @@ export function beforeEachProviders(fn: any /** TODO #9100 */): void {
|
||||
jsmBeforeEach(() => {
|
||||
var providers = fn();
|
||||
if (!providers) return;
|
||||
testInjector.addProviders(providers);
|
||||
testInjector.configureModule({providers: providers});
|
||||
});
|
||||
}
|
||||
|
||||
@ -135,11 +135,10 @@ function _it(jsmFn: Function, name: string, testFn: Function, testTimeOut: numbe
|
||||
provide: AsyncTestCompleter,
|
||||
useFactory: () => {
|
||||
// Mark the test as async when an AsyncTestCompleter is injected in an it()
|
||||
if (!inIt) throw new Error('AsyncTestCompleter can only be injected in an "it()"');
|
||||
return new AsyncTestCompleter();
|
||||
}
|
||||
};
|
||||
testInjector.addProviders([completerProvider]);
|
||||
testInjector.configureModule({providers: [completerProvider]});
|
||||
runner.run();
|
||||
|
||||
inIt = true;
|
||||
|
Reference in New Issue
Block a user