From c8f056beb696d176530ee4e86f64964c3b834c9a Mon Sep 17 00:00:00 2001 From: Adrian Rutkowski Date: Fri, 4 Sep 2020 22:57:21 +0200 Subject: [PATCH] fix(core): ensure TestBed is not instantiated before override provider (#38717) There is an inconsistency in overrideProvider behaviour. Testing documentation says (https://angular.io/guide/testing-components-basics#createcomponent) that all override... methods throw error if TestBed is already instantiated. However overrideProvider doesn't throw any error, but (same as other override... methods) doesn't replace providers if TestBed is instantiated. Add TestBed instantiation check to overrideProvider method to make it consistent. BREAKING CHANGE: If you call `TestBed.overrideProvider` after TestBed initialization, provider overrides are not applied. This behavior is consistent with other override methods (such as `TestBed.overrideDirective`, etc) but they throw an error to indicate that, when the check was missing in the `TestBed.overrideProvider` function. Now calling `TestBed.overrideProvider` after TestBed initialization also triggers an error, thus there is a chance that some tests (where `TestBed.overrideProvider` is called after TestBed initialization) will start to fail and require updates to move `TestBed.overrideProvider` calls before TestBed initialization is completed. Issue mentioned here: https://github.com/angular/angular/issues/13460#issuecomment-636005966 Documentation: https://angular.io/guide/testing-components-basics#createcomponent PR Close #38717 --- packages/core/testing/src/r3_test_bed.ts | 1 + packages/core/testing/src/test_bed.ts | 1 + .../test/testing_public_spec.ts | 76 ++++++++++++++++--- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/packages/core/testing/src/r3_test_bed.ts b/packages/core/testing/src/r3_test_bed.ts index 45aeae770f..74804de7d6 100644 --- a/packages/core/testing/src/r3_test_bed.ts +++ b/packages/core/testing/src/r3_test_bed.ts @@ -327,6 +327,7 @@ export class TestBedRender3 implements TestBed { */ overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}): void { + this.assertNotInstantiated('overrideProvider', 'override provider'); this.compiler.overrideProvider(token, provider); } diff --git a/packages/core/testing/src/test_bed.ts b/packages/core/testing/src/test_bed.ts index 7811c8947f..5a40de6703 100644 --- a/packages/core/testing/src/test_bed.ts +++ b/packages/core/testing/src/test_bed.ts @@ -535,6 +535,7 @@ export class TestBedViewEngine implements TestBed { overrideProvider(token: any, provider: {useValue: any;}): void; overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}): void { + this._assertNotInstantiated('overrideProvider', 'override provider'); this.overrideProviderImpl(token, provider); } diff --git a/packages/platform-browser/test/testing_public_spec.ts b/packages/platform-browser/test/testing_public_spec.ts index 073592ac46..bb6fa0e575 100644 --- a/packages/platform-browser/test/testing_public_spec.ts +++ b/packages/platform-browser/test/testing_public_spec.ts @@ -774,18 +774,6 @@ const bTok = new InjectionToken('b'); expect(testDir!.test).toBe('some prop'); }); - it('should throw if the TestBed is already created', () => { - @Component({selector: 'comp', template: 'a'}) - class MyComponent { - } - - TestBed.inject(Injector); - - expect(() => TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b')) - .toThrowError( - /Cannot override template when the test module has already been instantiated/); - }); - it('should reset overrides when the testing module is resetted', () => { @Component({selector: 'comp', template: 'a'}) class MyComponent { @@ -1071,5 +1059,69 @@ Did you run and wait for 'resolveComponentResources()'?` : expect(componentFixture.nativeElement).toHaveText('Parent(Mock)'); })); }); + + describe('calling override methods after TestBed initialization', () => { + const getExpectedErrorMessage = (methodName: string, methodDescription: string) => `Cannot ${ + methodDescription} when the test module has already been instantiated. Make sure you are not using \`inject\` before \`${ + methodName}\`.`; + + it('should throw if TestBed.overrideProvider is called after TestBed initialization', () => { + TestBed.inject(Injector); + + expect(() => TestBed.overrideProvider(aTok, { + useValue: 'mockValue' + })).toThrowError(getExpectedErrorMessage('overrideProvider', 'override provider')); + }); + + it('should throw if TestBed.overrideModule is called after TestBed initialization', () => { + @NgModule() + class MyModule { + } + + TestBed.inject(Injector); + + expect(() => TestBed.overrideModule(MyModule, {})) + .toThrowError(getExpectedErrorMessage('overrideModule', 'override module metadata')); + }); + + it('should throw if TestBed.overridePipe is called after TestBed initialization', () => { + @Pipe({name: 'myPipe'}) + class MyPipe { + transform(value: any) { + return value; + } + } + + TestBed.inject(Injector); + + expect(() => TestBed.overridePipe(MyPipe, {})) + .toThrowError(getExpectedErrorMessage('overridePipe', 'override pipe metadata')); + }); + + it('should throw if TestBed.overrideDirective is called after TestBed initialization', () => { + @Directive() + class MyDirective { + } + + TestBed.inject(Injector); + + expect(() => TestBed.overrideDirective(MyDirective, {})) + .toThrowError( + getExpectedErrorMessage('overrideDirective', 'override directive metadata')); + }); + + it('should throw if TestBed.overrideTemplateUsingTestingModule is called after TestBed initialization', + () => { + @Component({selector: 'comp', template: 'a'}) + class MyComponent { + } + + TestBed.inject(Injector); + + expect(() => TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b')) + .toThrowError( + /Cannot override template when the test module has already been instantiated/); + }); + }); }); }