From 796840209cd38aacc5061a31701efe7eda1f6587 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Wed, 29 Jun 2022 17:31:25 -0700 Subject: [PATCH] fix(core): align TestBed interfaces and implementation (#46635) This commit performs various refactoring of the TestBed code to better align interfaces and implementation. The implementation class is also renamed from `TestBedRender3` -> `TestBedImpl`, but the public API name has not changed. Note: as a part of this change, the TestBed interface became more consistent and typings for multiple methods were updated to account for the fact that the TestBed reference is returned. This was always a runtime behavior of TestBed, which was not reflected in few places in type. PR Close #46635 --- goldens/public-api/core/testing/index.md | 75 ++------ packages/core/test/test_bed_spec.ts | 70 ++++---- packages/core/testing/src/r3_test_bed.ts | 170 ++++++++++--------- packages/core/testing/src/test_bed.ts | 47 +++-- packages/core/testing/src/test_bed_common.ts | 93 +--------- packages/core/testing/src/test_hooks.ts | 8 +- packages/core/testing/src/testing.ts | 3 +- 7 files changed, 167 insertions(+), 299 deletions(-) diff --git a/goldens/public-api/core/testing/index.md b/goldens/public-api/core/testing/index.md index c87ce437d2ab6..64423af8c92c3 100644 --- a/goldens/public-api/core/testing/index.md +++ b/goldens/public-api/core/testing/index.md @@ -66,7 +66,7 @@ export function flush(maxTurns?: number): number; export function flushMicrotasks(): void; // @public -export const getTestBed: () => TestBed; +export function getTestBed(): TestBed; // @public export function inject(tokens: any[], fn: Function): () => any; @@ -104,7 +104,7 @@ export interface TestBed { useJit?: boolean; }): void; // (undocumented) - configureTestingModule(moduleDef: TestModuleMetadata): void; + configureTestingModule(moduleDef: TestModuleMetadata): TestBed; // (undocumented) createComponent(component: Type): ComponentFixture; // (undocumented) @@ -119,90 +119,47 @@ export interface TestBed { // (undocumented) inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T | null; // (undocumented) - ngModule: Type | Type[]; + get ngModule(): Type | Type[]; // (undocumented) - overrideComponent(component: Type, override: MetadataOverride): void; + overrideComponent(component: Type, override: MetadataOverride): TestBed; // (undocumented) - overrideDirective(directive: Type, override: MetadataOverride): void; + overrideDirective(directive: Type, override: MetadataOverride): TestBed; // (undocumented) - overrideModule(ngModule: Type, override: MetadataOverride): void; + overrideModule(ngModule: Type, override: MetadataOverride): TestBed; // (undocumented) - overridePipe(pipe: Type, override: MetadataOverride): void; + overridePipe(pipe: Type, override: MetadataOverride): TestBed; overrideProvider(token: any, provider: { useFactory: Function; deps: any[]; - }): void; + }): TestBed; // (undocumented) overrideProvider(token: any, provider: { useValue: any; - }): void; + }): TestBed; // (undocumented) overrideProvider(token: any, provider: { useFactory?: Function; useValue?: any; deps?: any[]; - }): void; + }): TestBed; // (undocumented) - overrideTemplateUsingTestingModule(component: Type, template: string): void; + overrideTemplate(component: Type, template: string): TestBed; // (undocumented) - platform: PlatformRef; + overrideTemplateUsingTestingModule(component: Type, template: string): TestBed; + // (undocumented) + get platform(): PlatformRef; resetTestEnvironment(): void; // (undocumented) - resetTestingModule(): void; + resetTestingModule(): TestBed; } // @public export const TestBed: TestBedStatic; // @public -export interface TestBedStatic { +export interface TestBedStatic extends TestBed { // (undocumented) new (...args: any[]): TestBed; - compileComponents(): Promise; - configureCompiler(config: { - providers?: any[]; - useJit?: boolean; - }): TestBedStatic; - configureTestingModule(moduleDef: TestModuleMetadata): TestBedStatic; - // (undocumented) - createComponent(component: Type): ComponentFixture; - // @deprecated (undocumented) - get(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): any; - // @deprecated (undocumented) - get(token: any, notFoundValue?: any): any; - initTestEnvironment(ngModule: Type | Type[], platform: PlatformRef, options?: TestEnvironmentOptions): TestBed; - // (undocumented) - inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; - // (undocumented) - inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T | null; - // (undocumented) - overrideComponent(component: Type, override: MetadataOverride): TestBedStatic; - // (undocumented) - overrideDirective(directive: Type, override: MetadataOverride): TestBedStatic; - // (undocumented) - overrideModule(ngModule: Type, override: MetadataOverride): TestBedStatic; - // (undocumented) - overridePipe(pipe: Type, override: MetadataOverride): TestBedStatic; - overrideProvider(token: any, provider: { - useFactory: Function; - deps: any[]; - }): TestBedStatic; - // (undocumented) - overrideProvider(token: any, provider: { - useValue: any; - }): TestBedStatic; - // (undocumented) - overrideProvider(token: any, provider: { - useFactory?: Function; - useValue?: any; - deps?: any[]; - }): TestBedStatic; - // (undocumented) - overrideTemplate(component: Type, template: string): TestBedStatic; - overrideTemplateUsingTestingModule(component: Type, template: string): TestBedStatic; - resetTestEnvironment(): void; - // (undocumented) - resetTestingModule(): TestBedStatic; } // @public diff --git a/packages/core/test/test_bed_spec.ts b/packages/core/test/test_bed_spec.ts index 219938f484bba..6735c7e819985 100644 --- a/packages/core/test/test_bed_spec.ts +++ b/packages/core/test/test_bed_spec.ts @@ -6,13 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {CommonModule} from '@angular/common'; import {APP_INITIALIZER, ChangeDetectorRef, Compiler, Component, Directive, ElementRef, ErrorHandler, getNgModuleById, Inject, Injectable, InjectionToken, Injector, Input, LOCALE_ID, ModuleWithProviders, NgModule, Optional, Pipe, Type, ViewChild, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelementEnd as elementEnd, ɵɵelementStart as elementStart, ɵɵsetNgModuleScope as setNgModuleScope, ɵɵtext as text} from '@angular/core'; -import {getTestBed, TestBed} from '@angular/core/testing/src/test_bed'; +import {TestBedImpl} from '@angular/core/testing/src/r3_test_bed'; +import {TestBed} from '@angular/core/testing/src/test_bed'; import {By} from '@angular/platform-browser'; import {expect} from '@angular/platform-browser/testing/src/matchers'; -import {TestBedRender3} from '../testing/src/r3_test_bed'; import {TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT, THROW_ON_UNKNOWN_ELEMENTS_DEFAULT, THROW_ON_UNKNOWN_PROPERTIES_DEFAULT} from '../testing/src/test_bed_common'; const NAME = new InjectionToken('name'); @@ -129,7 +128,7 @@ describe('TestBed', () => { it('should apply scopes correctly for components in the lazy-loaded module', () => { // Reset TestBed to the initial state, emulating an invocation of a first test. // Check `TestBed.checkGlobalCompilationFinished` for additional info. - (getTestBed() as any)._globalCompilationChecked = false; + TestBedImpl.INSTANCE.globalCompilationChecked = false; @Component({ selector: 'root', @@ -170,7 +169,7 @@ describe('TestBed', () => { describe('TestBed with Standalone types', () => { beforeEach(() => { - getTestBed().resetTestingModule(); + TestBed.resetTestingModule(); }); it('should override providers on standalone component itself', () => { @@ -498,7 +497,7 @@ describe('TestBed with Standalone types', () => { describe('TestBed', () => { beforeEach(() => { - getTestBed().resetTestingModule(); + TestBed.resetTestingModule(); TestBed.configureTestingModule({imports: [HelloWorldModule]}); }); @@ -681,7 +680,7 @@ describe('TestBed', () => { expect(hello.nativeElement).toHaveText('Hello World!'); // override the template - getTestBed().resetTestingModule(); + TestBed.resetTestingModule(); TestBed.configureTestingModule({imports: [HelloWorldModule]}); TestBed.overrideComponent(GreetingCmp, {set: {template: `Bonjour {{ name }}`}}); hello = TestBed.createComponent(HelloWorld); @@ -689,7 +688,7 @@ describe('TestBed', () => { expect(hello.nativeElement).toHaveText('Bonjour World!'); // restore the original template by calling `.resetTestingModule()` - getTestBed().resetTestingModule(); + TestBed.resetTestingModule(); TestBed.configureTestingModule({imports: [HelloWorldModule]}); hello = TestBed.createComponent(HelloWorld); hello.detectChanges(); @@ -1330,7 +1329,7 @@ describe('TestBed', () => { class ProvidesErrorHandler { } - getTestBed().resetTestingModule(); + TestBed.resetTestingModule(); TestBed.configureTestingModule({imports: [ProvidesErrorHandler, HelloWorldModule]}); expect(TestBed.inject(ErrorHandler)).toEqual(jasmine.any(CustomErrorHandler)); @@ -1859,28 +1858,24 @@ describe('TestBed', () => { describe('TestBed module teardown', () => { - // Cast the `TestBed` to the internal data type since we're testing private APIs. - let TestBed: TestBedRender3; - beforeEach(() => { - TestBed = getTestBed() as unknown as TestBedRender3; TestBed.resetTestingModule(); }); it('should tear down the test module by default', () => { - expect(TestBed.shouldTearDownTestingModule()).toBe(true); + expect(TestBedImpl.INSTANCE.shouldTearDownTestingModule()).toBe(true); }); it('should be able to configure the teardown behavior', () => { TestBed.configureTestingModule({teardown: {destroyAfterEach: false}}); - expect(TestBed.shouldTearDownTestingModule()).toBe(false); + expect(TestBedImpl.INSTANCE.shouldTearDownTestingModule()).toBe(false); }); it('should reset the teardown behavior back to the default when TestBed is reset', () => { TestBed.configureTestingModule({teardown: {destroyAfterEach: false}}); - expect(TestBed.shouldTearDownTestingModule()).toBe(false); + expect(TestBedImpl.INSTANCE.shouldTearDownTestingModule()).toBe(false); TestBed.resetTestingModule(); - expect(TestBed.shouldTearDownTestingModule()).toBe(true); + expect(TestBedImpl.INSTANCE.shouldTearDownTestingModule()).toBe(true); }); it('should destroy test module providers when test module teardown is enabled', () => { @@ -2053,88 +2048,83 @@ describe('TestBed module teardown', () => { }); it('should rethrow errors based on the default teardown behavior', () => { - expect(TestBed.shouldRethrowTeardownErrors()).toBe(TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT); + expect(TestBedImpl.INSTANCE.shouldRethrowTeardownErrors()) + .toBe(TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT); }); it('should rethrow errors if the option is omitted and test teardown is enabled', () => { TestBed.configureTestingModule({teardown: {destroyAfterEach: true}}); - expect(TestBed.shouldRethrowTeardownErrors()).toBe(true); + expect(TestBedImpl.INSTANCE.shouldRethrowTeardownErrors()).toBe(true); }); it('should not rethrow errors if the option is omitted and test teardown is disabled', () => { TestBed.configureTestingModule({teardown: {destroyAfterEach: false}}); - expect(TestBed.shouldRethrowTeardownErrors()).toBe(false); + expect(TestBedImpl.INSTANCE.shouldRethrowTeardownErrors()).toBe(false); }); it('should rethrow errors if the option is enabled, but teardown is disabled', () => { TestBed.configureTestingModule({teardown: {destroyAfterEach: false, rethrowErrors: true}}); - expect(TestBed.shouldRethrowTeardownErrors()).toBe(true); + expect(TestBedImpl.INSTANCE.shouldRethrowTeardownErrors()).toBe(true); }); it('should not rethrow errors if the option is disabled, but teardown is enabled', () => { TestBed.configureTestingModule({teardown: {destroyAfterEach: true, rethrowErrors: false}}); - expect(TestBed.shouldRethrowTeardownErrors()).toBe(false); + expect(TestBedImpl.INSTANCE.shouldRethrowTeardownErrors()).toBe(false); }); }); describe('TestBed module `errorOnUnknownElements`', () => { - // Cast the `TestBed` to the internal data type since we're testing private APIs. - let TestBed: TestBedRender3; - beforeEach(() => { - TestBed = getTestBed() as unknown as TestBedRender3; TestBed.resetTestingModule(); }); it('should not throw based on the default behavior', () => { - expect(TestBed.shouldThrowErrorOnUnknownElements()).toBe(THROW_ON_UNKNOWN_ELEMENTS_DEFAULT); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownElements()) + .toBe(THROW_ON_UNKNOWN_ELEMENTS_DEFAULT); }); it('should not throw if the option is omitted', () => { TestBed.configureTestingModule({}); - expect(TestBed.shouldThrowErrorOnUnknownElements()).toBe(false); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownElements()).toBe(false); }); it('should be able to configure the option', () => { TestBed.configureTestingModule({errorOnUnknownElements: true}); - expect(TestBed.shouldThrowErrorOnUnknownElements()).toBe(true); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownElements()).toBe(true); }); it('should reset the option back to the default when TestBed is reset', () => { TestBed.configureTestingModule({errorOnUnknownElements: true}); - expect(TestBed.shouldThrowErrorOnUnknownElements()).toBe(true); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownElements()).toBe(true); TestBed.resetTestingModule(); - expect(TestBed.shouldThrowErrorOnUnknownElements()).toBe(false); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownElements()).toBe(false); }); }); describe('TestBed module `errorOnUnknownProperties`', () => { - // Cast the `TestBed` to the internal data type since we're testing private APIs. - let TestBed: TestBedRender3; - beforeEach(() => { - TestBed = getTestBed() as unknown as TestBedRender3; TestBed.resetTestingModule(); }); it('should not throw based on the default behavior', () => { - expect(TestBed.shouldThrowErrorOnUnknownProperties()).toBe(THROW_ON_UNKNOWN_PROPERTIES_DEFAULT); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownProperties()) + .toBe(THROW_ON_UNKNOWN_PROPERTIES_DEFAULT); }); it('should not throw if the option is omitted', () => { TestBed.configureTestingModule({}); - expect(TestBed.shouldThrowErrorOnUnknownProperties()).toBe(false); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownProperties()).toBe(false); }); it('should be able to configure the option', () => { TestBed.configureTestingModule({errorOnUnknownProperties: true}); - expect(TestBed.shouldThrowErrorOnUnknownProperties()).toBe(true); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownProperties()).toBe(true); }); it('should reset the option back to the default when TestBed is reset', () => { TestBed.configureTestingModule({errorOnUnknownProperties: true}); - expect(TestBed.shouldThrowErrorOnUnknownProperties()).toBe(true); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownProperties()).toBe(true); TestBed.resetTestingModule(); - expect(TestBed.shouldThrowErrorOnUnknownProperties()).toBe(false); + expect(TestBedImpl.INSTANCE.shouldThrowErrorOnUnknownProperties()).toBe(false); }); }); diff --git a/packages/core/testing/src/r3_test_bed.ts b/packages/core/testing/src/r3_test_bed.ts index 0e0629c162c7f..00f5413d302a1 100644 --- a/packages/core/testing/src/r3_test_bed.ts +++ b/packages/core/testing/src/r3_test_bed.ts @@ -41,10 +41,19 @@ import {ComponentFixture} from './component_fixture'; import {MetadataOverride} from './metadata_override'; import {R3TestBedCompiler} from './r3_test_bed_compiler'; import {TestBed} from './test_bed'; -import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, ModuleTeardownOptions, TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT, TestBedStatic, TestComponentRenderer, TestEnvironmentOptions, TestModuleMetadata, THROW_ON_UNKNOWN_ELEMENTS_DEFAULT, THROW_ON_UNKNOWN_PROPERTIES_DEFAULT} from './test_bed_common'; +import {ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, ModuleTeardownOptions, TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT, TestComponentRenderer, TestEnvironmentOptions, TestModuleMetadata, THROW_ON_UNKNOWN_ELEMENTS_DEFAULT, THROW_ON_UNKNOWN_PROPERTIES_DEFAULT} from './test_bed_common'; let _nextRootElementId = 0; +/** + * Returns a singleton of the `TestBed` class. + * + * @publicApi + */ +export function getTestBed(): TestBed { + return TestBedImpl.INSTANCE; +} + /** * @description @@ -52,11 +61,14 @@ let _nextRootElementId = 0; * creating components and services in unit tests. * * TestBed is the primary api for writing unit tests for Angular applications and libraries. - * - * Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3` - * according to the compiler used. */ -export class TestBedRender3 implements TestBed { +export class TestBedImpl implements TestBed { + private static _INSTANCE: TestBedImpl|null = null; + + static get INSTANCE(): TestBedImpl { + return TestBedImpl._INSTANCE = TestBedImpl._INSTANCE || new TestBedImpl(); + } + /** * Teardown options that have been configured at the environment level. * Used as a fallback if no instance-level options have been provided. @@ -121,7 +133,7 @@ export class TestBedRender3 implements TestBed { static initTestEnvironment( ngModule: Type|Type[], platform: PlatformRef, options?: TestEnvironmentOptions): TestBed { - const testBed = _getTestBedRender3(); + const testBed = TestBedImpl.INSTANCE; testBed.initTestEnvironment(ngModule, platform, options); return testBed; } @@ -132,21 +144,19 @@ export class TestBedRender3 implements TestBed { * @publicApi */ static resetTestEnvironment(): void { - _getTestBedRender3().resetTestEnvironment(); + TestBedImpl.INSTANCE.resetTestEnvironment(); } - static configureCompiler(config: {providers?: any[]; useJit?: boolean;}): TestBedStatic { - _getTestBedRender3().configureCompiler(config); - return TestBedRender3 as any as TestBedStatic; + static configureCompiler(config: {providers?: any[]; useJit?: boolean;}): TestBed { + return TestBedImpl.INSTANCE.configureCompiler(config); } /** * Allows overriding default providers, directives, pipes, modules of the test injector, * which are defined in test_injector.js */ - static configureTestingModule(moduleDef: TestModuleMetadata): TestBedStatic { - _getTestBedRender3().configureTestingModule(moduleDef); - return TestBedRender3 as any as TestBedStatic; + static configureTestingModule(moduleDef: TestModuleMetadata): TestBed { + return TestBedImpl.INSTANCE.configureTestingModule(moduleDef); } /** @@ -155,34 +165,27 @@ export class TestBedRender3 implements TestBed { * as fetching urls is asynchronous. */ static compileComponents(): Promise { - return _getTestBedRender3().compileComponents(); + return TestBedImpl.INSTANCE.compileComponents(); } - static overrideModule(ngModule: Type, override: MetadataOverride): TestBedStatic { - _getTestBedRender3().overrideModule(ngModule, override); - return TestBedRender3 as any as TestBedStatic; + static overrideModule(ngModule: Type, override: MetadataOverride): TestBed { + return TestBedImpl.INSTANCE.overrideModule(ngModule, override); } - static overrideComponent(component: Type, override: MetadataOverride): - TestBedStatic { - _getTestBedRender3().overrideComponent(component, override); - return TestBedRender3 as any as TestBedStatic; + static overrideComponent(component: Type, override: MetadataOverride): TestBed { + return TestBedImpl.INSTANCE.overrideComponent(component, override); } - static overrideDirective(directive: Type, override: MetadataOverride): - TestBedStatic { - _getTestBedRender3().overrideDirective(directive, override); - return TestBedRender3 as any as TestBedStatic; + static overrideDirective(directive: Type, override: MetadataOverride): TestBed { + return TestBedImpl.INSTANCE.overrideDirective(directive, override); } - static overridePipe(pipe: Type, override: MetadataOverride): TestBedStatic { - _getTestBedRender3().overridePipe(pipe, override); - return TestBedRender3 as any as TestBedStatic; + static overridePipe(pipe: Type, override: MetadataOverride): TestBed { + return TestBedImpl.INSTANCE.overridePipe(pipe, override); } - static overrideTemplate(component: Type, template: string): TestBedStatic { - _getTestBedRender3().overrideComponent(component, {set: {template, templateUrl: null!}}); - return TestBedRender3 as any as TestBedStatic; + static overrideTemplate(component: Type, template: string): TestBed { + return TestBedImpl.INSTANCE.overrideTemplate(component, template); } /** @@ -191,29 +194,27 @@ export class TestBedRender3 implements TestBed { * * Note: This works for JIT and AOTed components as well. */ - static overrideTemplateUsingTestingModule(component: Type, template: string): TestBedStatic { - _getTestBedRender3().overrideTemplateUsingTestingModule(component, template); - return TestBedRender3 as any as TestBedStatic; + static overrideTemplateUsingTestingModule(component: Type, template: string): TestBed { + return TestBedImpl.INSTANCE.overrideTemplateUsingTestingModule(component, template); } static overrideProvider(token: any, provider: { useFactory: Function, deps: any[], - }): TestBedStatic; - static overrideProvider(token: any, provider: {useValue: any;}): TestBedStatic; + }): TestBed; + static overrideProvider(token: any, provider: {useValue: any;}): TestBed; static overrideProvider(token: any, provider: { useFactory?: Function, useValue?: any, deps?: any[], - }): TestBedStatic { - _getTestBedRender3().overrideProvider(token, provider); - return TestBedRender3 as any as TestBedStatic; + }): TestBed { + return TestBedImpl.INSTANCE.overrideProvider(token, provider); } static inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; static inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T|null; static inject(token: ProviderToken, notFoundValue?: T|null, flags?: InjectFlags): T|null { - return _getTestBedRender3().inject(token, notFoundValue, flags); + return TestBedImpl.INSTANCE.inject(token, notFoundValue, flags); } /** @deprecated from v9.0.0 use TestBed.inject */ @@ -224,24 +225,27 @@ export class TestBedRender3 implements TestBed { static get( token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND, flags: InjectFlags = InjectFlags.Default): any { - return _getTestBedRender3().inject(token, notFoundValue, flags); + return TestBedImpl.INSTANCE.inject(token, notFoundValue, flags); } static createComponent(component: Type): ComponentFixture { - return _getTestBedRender3().createComponent(component); + return TestBedImpl.INSTANCE.createComponent(component); } - static resetTestingModule(): TestBedStatic { - _getTestBedRender3().resetTestingModule(); - return TestBedRender3 as any as TestBedStatic; + static resetTestingModule(): TestBed { + return TestBedImpl.INSTANCE.resetTestingModule(); } - static shouldTearDownTestingModule(): boolean { - return _getTestBedRender3().shouldTearDownTestingModule(); + static execute(tokens: any[], fn: Function, context?: any): any { + return TestBedImpl.INSTANCE.execute(tokens, fn, context); } - static tearDownTestingModule(): void { - _getTestBedRender3().tearDownTestingModule(); + static get platform(): PlatformRef { + return TestBedImpl.INSTANCE.platform; + } + + static get ngModule(): Type|Type[] { + return TestBedImpl.INSTANCE.ngModule; } // Properties @@ -253,7 +257,13 @@ export class TestBedRender3 implements TestBed { private _testModuleRef: NgModuleRef|null = null; private _activeFixtures: ComponentFixture[] = []; - private _globalCompilationChecked = false; + + /** + * Internal-only flag to indicate whether a module + * scoping queue has been checked and flushed already. + * @nodoc + */ + globalCompilationChecked = false; /** * Initialize the environment for testing with a compiler factory, a PlatformRef, and an @@ -275,11 +285,11 @@ export class TestBedRender3 implements TestBed { throw new Error('Cannot set base providers because it has already been called'); } - TestBedRender3._environmentTeardownOptions = options?.teardown; + TestBedImpl._environmentTeardownOptions = options?.teardown; - TestBedRender3._environmentErrorOnUnknownElementsOption = options?.errorOnUnknownElements; + TestBedImpl._environmentErrorOnUnknownElementsOption = options?.errorOnUnknownElements; - TestBedRender3._environmentErrorOnUnknownPropertiesOption = options?.errorOnUnknownProperties; + TestBedImpl._environmentErrorOnUnknownPropertiesOption = options?.errorOnUnknownProperties; this.platform = platform; this.ngModule = ngModule; @@ -302,11 +312,11 @@ export class TestBedRender3 implements TestBed { this._compiler = null; this.platform = null!; this.ngModule = null!; - TestBedRender3._environmentTeardownOptions = undefined; + TestBedImpl._environmentTeardownOptions = undefined; setAllowDuplicateNgModuleIdsForTest(false); } - resetTestingModule(): void { + resetTestingModule(): this { this.checkGlobalCompilationFinished(); resetCompiledComponents(); if (this._compiler !== null) { @@ -337,9 +347,10 @@ export class TestBedRender3 implements TestBed { this._instanceErrorOnUnknownPropertiesOption = undefined; } } + return this; } - configureCompiler(config: {providers?: any[]; useJit?: boolean;}): void { + configureCompiler(config: {providers?: any[]; useJit?: boolean;}): this { if (config.useJit != null) { throw new Error('the Render3 compiler JiT mode is not configurable !'); } @@ -347,9 +358,10 @@ export class TestBedRender3 implements TestBed { if (config.providers !== undefined) { this.compiler.setCompilerProviders(config.providers); } + return this; } - configureTestingModule(moduleDef: TestModuleMetadata): void { + configureTestingModule(moduleDef: TestModuleMetadata): this { this.assertNotInstantiated('R3TestBed.configureTestingModule', 'configure the test module'); // Trigger module scoping queue flush before executing other TestBed operations in a test. @@ -370,6 +382,7 @@ export class TestBedRender3 implements TestBed { this._previousErrorOnUnknownPropertiesOption = getUnknownPropertyStrictMode(); setUnknownPropertyStrictMode(this.shouldThrowErrorOnUnknownProperties()); this.compiler.configureTestingModule(moduleDef); + return this; } compileComponents(): Promise { @@ -379,7 +392,7 @@ export class TestBedRender3 implements TestBed { inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T|null; inject(token: ProviderToken, notFoundValue?: T|null, flags?: InjectFlags): T|null { - if (token as unknown === TestBedRender3) { + if (token as unknown === TestBed) { return this as any; } const UNDEFINED = {} as unknown as T; @@ -403,40 +416,50 @@ export class TestBedRender3 implements TestBed { return fn.apply(context, params); } - overrideModule(ngModule: Type, override: MetadataOverride): void { + overrideModule(ngModule: Type, override: MetadataOverride): this { this.assertNotInstantiated('overrideModule', 'override module metadata'); this.compiler.overrideModule(ngModule, override); + return this; } - overrideComponent(component: Type, override: MetadataOverride): void { + overrideComponent(component: Type, override: MetadataOverride): this { this.assertNotInstantiated('overrideComponent', 'override component metadata'); this.compiler.overrideComponent(component, override); + return this; } - overrideTemplateUsingTestingModule(component: Type, template: string): void { + overrideTemplateUsingTestingModule(component: Type, template: string): this { this.assertNotInstantiated( 'R3TestBed.overrideTemplateUsingTestingModule', 'Cannot override template when the test module has already been instantiated'); this.compiler.overrideTemplateUsingTestingModule(component, template); + return this; } - overrideDirective(directive: Type, override: MetadataOverride): void { + overrideDirective(directive: Type, override: MetadataOverride): this { this.assertNotInstantiated('overrideDirective', 'override directive metadata'); this.compiler.overrideDirective(directive, override); + return this; } - overridePipe(pipe: Type, override: MetadataOverride): void { + overridePipe(pipe: Type, override: MetadataOverride): this { this.assertNotInstantiated('overridePipe', 'override pipe metadata'); this.compiler.overridePipe(pipe, override); + return this; } /** * Overwrites all providers for the given token with the given provider definition. */ overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}): - void { + this { this.assertNotInstantiated('overrideProvider', 'override provider'); this.compiler.overrideProvider(token, provider); + return this; + } + + overrideTemplate(component: Type, template: string): TestBed { + return this.overrideComponent(component, {set: {template, templateUrl: null!}}); } createComponent(type: Type): ComponentFixture { @@ -512,10 +535,10 @@ export class TestBedRender3 implements TestBed { private checkGlobalCompilationFinished(): void { // Checking _testNgModuleRef is null should not be necessary, but is left in as an additional // guard that compilations queued in tests (after instantiation) are never flushed accidentally. - if (!this._globalCompilationChecked && this._testModuleRef === null) { + if (!this.globalCompilationChecked && this._testModuleRef === null) { flushModuleScopingQueueAsMuchAsPossible(); } - this._globalCompilationChecked = true; + this.globalCompilationChecked = true; } private destroyActiveFixtures(): void { @@ -542,7 +565,7 @@ export class TestBedRender3 implements TestBed { shouldRethrowTeardownErrors(): boolean { const instanceOptions = this._instanceTeardownOptions; - const environmentOptions = TestBedRender3._environmentTeardownOptions; + const environmentOptions = TestBedImpl._environmentTeardownOptions; // If the new teardown behavior hasn't been configured, preserve the old behavior. if (!instanceOptions && !environmentOptions) { @@ -557,20 +580,19 @@ export class TestBedRender3 implements TestBed { shouldThrowErrorOnUnknownElements(): boolean { // Check if a configuration has been provided to throw when an unknown element is found return this._instanceErrorOnUnknownElementsOption ?? - TestBedRender3._environmentErrorOnUnknownElementsOption ?? - THROW_ON_UNKNOWN_ELEMENTS_DEFAULT; + TestBedImpl._environmentErrorOnUnknownElementsOption ?? THROW_ON_UNKNOWN_ELEMENTS_DEFAULT; } shouldThrowErrorOnUnknownProperties(): boolean { // Check if a configuration has been provided to throw when an unknown property is found return this._instanceErrorOnUnknownPropertiesOption ?? - TestBedRender3._environmentErrorOnUnknownPropertiesOption ?? + TestBedImpl._environmentErrorOnUnknownPropertiesOption ?? THROW_ON_UNKNOWN_PROPERTIES_DEFAULT; } shouldTearDownTestingModule(): boolean { return this._instanceTeardownOptions?.destroyAfterEach ?? - TestBedRender3._environmentTeardownOptions?.destroyAfterEach ?? + TestBedImpl._environmentTeardownOptions?.destroyAfterEach ?? TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT; } @@ -598,9 +620,3 @@ export class TestBedRender3 implements TestBed { } } } - -let testBed: TestBedRender3; - -export function _getTestBedRender3(): TestBedRender3 { - return testBed = testBed || new TestBedRender3(); -} diff --git a/packages/core/testing/src/test_bed.ts b/packages/core/testing/src/test_bed.ts index 8e673972bebf5..88af19bae2e23 100644 --- a/packages/core/testing/src/test_bed.ts +++ b/packages/core/testing/src/test_bed.ts @@ -10,16 +10,16 @@ import {Component, Directive, InjectFlags, NgModule, Pipe, PlatformRef, Provider import {ComponentFixture} from './component_fixture'; import {MetadataOverride} from './metadata_override'; -import {_getTestBedRender3, TestBedRender3} from './r3_test_bed'; +import {TestBedImpl} from './r3_test_bed'; import {TestBedStatic, TestEnvironmentOptions, TestModuleMetadata} from './test_bed_common'; /** * @publicApi */ export interface TestBed { - platform: PlatformRef; + get platform(): PlatformRef; - ngModule: Type|Type[]; + get ngModule(): Type|Type[]; /** * Initialize the environment for testing with a compiler factory, a PlatformRef, and an @@ -41,11 +41,11 @@ export interface TestBed { */ resetTestEnvironment(): void; - resetTestingModule(): void; + resetTestingModule(): TestBed; configureCompiler(config: {providers?: any[], useJit?: boolean}): void; - configureTestingModule(moduleDef: TestModuleMetadata): void; + configureTestingModule(moduleDef: TestModuleMetadata): TestBed; compileComponents(): Promise; @@ -59,13 +59,15 @@ export interface TestBed { execute(tokens: any[], fn: Function, context?: any): any; - overrideModule(ngModule: Type, override: MetadataOverride): void; + overrideModule(ngModule: Type, override: MetadataOverride): TestBed; - overrideComponent(component: Type, override: MetadataOverride): void; + overrideComponent(component: Type, override: MetadataOverride): TestBed; - overrideDirective(directive: Type, override: MetadataOverride): void; + overrideDirective(directive: Type, override: MetadataOverride): TestBed; - overridePipe(pipe: Type, override: MetadataOverride): void; + overridePipe(pipe: Type, override: MetadataOverride): TestBed; + + overrideTemplate(component: Type, template: string): TestBed; /** * Overwrites all providers for the given token with the given provider definition. @@ -73,12 +75,12 @@ export interface TestBed { overrideProvider(token: any, provider: { useFactory: Function, deps: any[], - }): void; - overrideProvider(token: any, provider: {useValue: any;}): void; + }): TestBed; + overrideProvider(token: any, provider: {useValue: any;}): TestBed; overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}): - void; + TestBed; - overrideTemplateUsingTestingModule(component: Type, template: string): void; + overrideTemplateUsingTestingModule(component: Type, template: string): TestBed; createComponent(component: Type): ComponentFixture; } @@ -90,21 +92,10 @@ export interface TestBed { * * `TestBed` is the primary api for writing unit tests for Angular applications and libraries. * - * Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3` - * according to the compiler used. - * * @publicApi */ -export const TestBed: TestBedStatic = TestBedRender3; +export const TestBed: TestBedStatic = TestBedImpl; -/** - * Returns a singleton of the applicable `TestBed`. - * - * It will be either an instance of `TestBedViewEngine` or `TestBedRender3`. - * - * @publicApi - */ -export const getTestBed: () => TestBed = _getTestBedRender3; /** * Allows injecting dependencies in `beforeEach()` and `it()`. Note: this function @@ -129,7 +120,7 @@ export const getTestBed: () => TestBed = _getTestBedRender3; * @publicApi */ export function inject(tokens: any[], fn: Function): () => any { - const testBed = getTestBed(); + const testBed = TestBedImpl.INSTANCE; // Not using an arrow function to preserve context passed from call site return function(this: unknown) { return testBed.execute(tokens, fn, this); @@ -145,7 +136,7 @@ export class InjectSetupWrapper { private _addModule() { const moduleDef = this._moduleDef(); if (moduleDef) { - getTestBed().configureTestingModule(moduleDef); + TestBedImpl.configureTestingModule(moduleDef); } } @@ -169,7 +160,7 @@ export function withModule(moduleDef: TestModuleMetadata, fn?: Function|null): ( if (fn) { // Not using an arrow function to preserve context passed from call site return function(this: unknown) { - const testBed = getTestBed(); + const testBed = TestBedImpl.INSTANCE; if (moduleDef) { testBed.configureTestingModule(moduleDef); } diff --git a/packages/core/testing/src/test_bed_common.ts b/packages/core/testing/src/test_bed_common.ts index 3ed811378769b..1328f15fb1db7 100644 --- a/packages/core/testing/src/test_bed_common.ts +++ b/packages/core/testing/src/test_bed_common.ts @@ -6,10 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, InjectFlags, InjectionToken, NgModule, Pipe, PlatformRef, ProviderToken, SchemaMetadata, Type} from '@angular/core'; +import {InjectionToken, SchemaMetadata} from '@angular/core'; -import {ComponentFixture} from './component_fixture'; -import {MetadataOverride} from './metadata_override'; import {TestBed} from './test_bed'; /** Whether test modules should be torn down by default. */ @@ -104,95 +102,10 @@ export interface ModuleTeardownOptions { } /** - * Static methods implemented by the `TestBedViewEngine` and `TestBedRender3` + * Static methods implemented by the `TestBed`. * * @publicApi */ -export interface TestBedStatic { +export interface TestBedStatic extends TestBed { new(...args: any[]): TestBed; - - /** - * Initialize the environment for testing with a compiler factory, a PlatformRef, and an - * angular module. These are common to every test in the suite. - * - * 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 `resetTestEnvironment`. - * - * Test modules and platforms for individual platforms are available from - * '@angular//testing'. - */ - initTestEnvironment( - ngModule: Type|Type[], platform: PlatformRef, - options?: TestEnvironmentOptions): TestBed; - - /** - * Reset the providers for the test injector. - */ - resetTestEnvironment(): void; - - resetTestingModule(): TestBedStatic; - - /** - * Allows overriding default compiler providers and settings - * which are defined in test_injector.js - */ - configureCompiler(config: {providers?: any[]; useJit?: boolean;}): TestBedStatic; - - /** - * Allows overriding default providers, directives, pipes, modules of the test injector, - * which are defined in test_injector.js - */ - configureTestingModule(moduleDef: TestModuleMetadata): TestBedStatic; - - /** - * Compile components with a `templateUrl` for the test's NgModule. - * It is necessary to call this function - * as fetching urls is asynchronous. - */ - compileComponents(): Promise; - - overrideModule(ngModule: Type, override: MetadataOverride): TestBedStatic; - - overrideComponent(component: Type, override: MetadataOverride): TestBedStatic; - - overrideDirective(directive: Type, override: MetadataOverride): TestBedStatic; - - overridePipe(pipe: Type, override: MetadataOverride): TestBedStatic; - - overrideTemplate(component: Type, template: string): TestBedStatic; - - /** - * Overrides the template of the given component, compiling the template - * in the context of the TestingModule. - * - * Note: This works for JIT and AOTed components as well. - */ - overrideTemplateUsingTestingModule(component: Type, template: string): TestBedStatic; - - /** - * Overwrites all providers for the given token with the given provider definition. - * - * Note: This works for JIT and AOTed components as well. - */ - overrideProvider(token: any, provider: { - useFactory: Function, - deps: any[], - }): TestBedStatic; - overrideProvider(token: any, provider: {useValue: any;}): TestBedStatic; - overrideProvider(token: any, provider: { - useFactory?: Function, - useValue?: any, - deps?: any[], - }): TestBedStatic; - - inject(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): T; - inject(token: ProviderToken, notFoundValue: null, flags?: InjectFlags): T|null; - - /** @deprecated from v9.0.0 use TestBed.inject */ - get(token: ProviderToken, notFoundValue?: T, flags?: InjectFlags): any; - /** @deprecated from v9.0.0 use TestBed.inject */ - get(token: any, notFoundValue?: any): any; - - createComponent(component: Type): ComponentFixture; } diff --git a/packages/core/testing/src/test_hooks.ts b/packages/core/testing/src/test_hooks.ts index b2873c5903af3..a3103b5873281 100644 --- a/packages/core/testing/src/test_hooks.ts +++ b/packages/core/testing/src/test_hooks.ts @@ -13,7 +13,7 @@ */ import {resetFakeAsyncZone} from './fake_async'; -import {TestBed} from './test_bed'; +import {TestBedImpl} from './r3_test_bed'; declare var global: any; @@ -33,9 +33,9 @@ if (_global.afterEach) { function getCleanupHook(expectedTeardownValue: boolean) { return () => { - // TODO(alxhub): find a better type here - if ((TestBed as any).shouldTearDownTestingModule() === expectedTeardownValue) { - TestBed.resetTestingModule(); + const testBed = TestBedImpl.INSTANCE; + if (testBed.shouldTearDownTestingModule() === expectedTeardownValue) { + testBed.resetTestingModule(); resetFakeAsyncZone(); } }; diff --git a/packages/core/testing/src/testing.ts b/packages/core/testing/src/testing.ts index 00a76b3fe633d..2af3d5ffa2e89 100644 --- a/packages/core/testing/src/testing.ts +++ b/packages/core/testing/src/testing.ts @@ -15,8 +15,9 @@ export * from './async'; export * from './component_fixture'; export * from './fake_async'; -export {TestBed, getTestBed, inject, InjectSetupWrapper, withModule} from './test_bed'; +export {TestBed, inject, InjectSetupWrapper, withModule} from './test_bed'; export {TestComponentRenderer, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, TestModuleMetadata, TestEnvironmentOptions, ModuleTeardownOptions, TestBedStatic} from './test_bed_common'; export * from './test_hooks'; +export {getTestBed} from './r3_test_bed'; export * from './metadata_override'; export {MetadataOverrider as ɵMetadataOverrider} from './metadata_overrider';