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
This commit is contained in:
Adrian Rutkowski 2020-09-04 22:57:21 +02:00 committed by Misko Hevery
parent a2068523fd
commit c8f056beb6
3 changed files with 66 additions and 12 deletions

View File

@ -327,6 +327,7 @@ export class TestBedRender3 implements TestBed {
*/ */
overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}): overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}):
void { void {
this.assertNotInstantiated('overrideProvider', 'override provider');
this.compiler.overrideProvider(token, provider); this.compiler.overrideProvider(token, provider);
} }

View File

@ -535,6 +535,7 @@ export class TestBedViewEngine implements TestBed {
overrideProvider(token: any, provider: {useValue: any;}): void; overrideProvider(token: any, provider: {useValue: any;}): void;
overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}): overrideProvider(token: any, provider: {useFactory?: Function, useValue?: any, deps?: any[]}):
void { void {
this._assertNotInstantiated('overrideProvider', 'override provider');
this.overrideProviderImpl(token, provider); this.overrideProviderImpl(token, provider);
} }

View File

@ -774,18 +774,6 @@ const bTok = new InjectionToken<string>('b');
expect(testDir!.test).toBe('some prop'); 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', () => { it('should reset overrides when the testing module is resetted', () => {
@Component({selector: 'comp', template: 'a'}) @Component({selector: 'comp', template: 'a'})
class MyComponent { class MyComponent {
@ -1071,5 +1059,69 @@ Did you run and wait for 'resolveComponentResources()'?` :
expect(componentFixture.nativeElement).toHaveText('Parent(Mock)'); 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/);
});
});
}); });
} }