diff --git a/packages/core/test/test_bed_spec.ts b/packages/core/test/test_bed_spec.ts
index 041c5e7be1..160cd24b28 100644
--- a/packages/core/test/test_bed_spec.ts
+++ b/packages/core/test/test_bed_spec.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Compiler, Component, Directive, ErrorHandler, Inject, Injectable, InjectionToken, Injector, Input, ModuleWithProviders, NgModule, Optional, Pipe, ViewChild, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineNgModule as defineNgModule, ɵɵtext as text} from '@angular/core';
+import {Compiler, Component, Directive, ErrorHandler, Inject, Injectable, InjectionToken, Injector, Input, ModuleWithProviders, NgModule, Optional, Pipe, Type, ViewChild, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineNgModule as defineNgModule, ɵɵtext as text} from '@angular/core';
import {TestBed, getTestBed} from '@angular/core/testing/src/test_bed';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@@ -715,6 +715,36 @@ describe('TestBed', () => {
expect(fixture.nativeElement.innerHTML).toEqual('Inner');
});
+ onlyInIvy('Ivy-specific errors').describe('checking types before compiling them', () => {
+ @Directive({
+ selector: 'my-dir',
+ })
+ class MyDir {
+ }
+
+ @NgModule()
+ class MyModule {
+ }
+
+ // [decorator, type, overrideFn]
+ const cases: [string, Type, string][] = [
+ ['Component', MyDir, 'overrideComponent'],
+ ['NgModule', MyDir, 'overrideModule'],
+ ['Pipe', MyModule, 'overridePipe'],
+ ['Directive', MyModule, 'overrideDirective'],
+ ];
+ cases.forEach(([decorator, type, overrideFn]) => {
+ it(`should throw an error in case invalid type is used in ${overrideFn} function`, () => {
+ TestBed.configureTestingModule({declarations: [MyDir]});
+ expect(() => {
+ (TestBed as any)[overrideFn](type, {});
+ TestBed.createComponent(type);
+ }).toThrowError(new RegExp(`class doesn't have @${decorator} decorator`, 'g'));
+ });
+ });
+ });
+
+
onlyInIvy('TestBed should handle AOT pre-compiled Components')
.describe('AOT pre-compiled components', () => {
/**
diff --git a/packages/core/testing/src/r3_test_bed_compiler.ts b/packages/core/testing/src/r3_test_bed_compiler.ts
index 9f17920c52..5c35e420ef 100644
--- a/packages/core/testing/src/r3_test_bed_compiler.ts
+++ b/packages/core/testing/src/r3_test_bed_compiler.ts
@@ -129,10 +129,10 @@ export class R3TestBedCompiler {
this.resolvers.module.addOverride(ngModule, override);
const metadata = this.resolvers.module.resolve(ngModule);
if (metadata === null) {
- throw new Error(`${ngModule.name} is not an @NgModule or is missing metadata`);
+ throw invalidTypeError(ngModule.name, 'NgModule');
}
- this.recompileNgModule(ngModule);
+ this.recompileNgModule(ngModule, metadata);
// At this point, the module has a valid module def (ɵmod), but the override may have introduced
// new declarations or imported modules. Ingest any possible new types and add them to the
@@ -306,7 +306,10 @@ export class R3TestBedCompiler {
let needsAsyncResources = false;
this.pendingComponents.forEach(declaration => {
needsAsyncResources = needsAsyncResources || isComponentDefPendingResolution(declaration);
- const metadata = this.resolvers.component.resolve(declaration) !;
+ const metadata = this.resolvers.component.resolve(declaration);
+ if (metadata === null) {
+ throw invalidTypeError(declaration.name, 'Component');
+ }
this.maybeStoreNgDef(NG_COMP_DEF, declaration);
compileComponent(declaration, metadata);
});
@@ -314,13 +317,19 @@ export class R3TestBedCompiler {
this.pendingDirectives.forEach(declaration => {
const metadata = this.resolvers.directive.resolve(declaration);
+ if (metadata === null) {
+ throw invalidTypeError(declaration.name, 'Directive');
+ }
this.maybeStoreNgDef(NG_DIR_DEF, declaration);
compileDirective(declaration, metadata);
});
this.pendingDirectives.clear();
this.pendingPipes.forEach(declaration => {
- const metadata = this.resolvers.pipe.resolve(declaration) !;
+ const metadata = this.resolvers.pipe.resolve(declaration);
+ if (metadata === null) {
+ throw invalidTypeError(declaration.name, 'Pipe');
+ }
this.maybeStoreNgDef(NG_PIPE_DEF, declaration);
compilePipe(declaration, metadata);
});
@@ -434,11 +443,7 @@ export class R3TestBedCompiler {
}
}
- private recompileNgModule(ngModule: Type): void {
- const metadata = this.resolvers.module.resolve(ngModule);
- if (metadata === null) {
- throw new Error(`Unable to resolve metadata for NgModule: ${ngModule.name}`);
- }
+ private recompileNgModule(ngModule: Type, metadata: NgModule): void {
// Cache the initial ngModuleDef as it will be overwritten.
this.maybeStoreNgDef(NG_MOD_DEF, ngModule);
this.maybeStoreNgDef(NG_INJ_DEF, ngModule);
@@ -751,6 +756,10 @@ function forEachRight(values: T[], fn: (value: T, idx: number) => void): void
}
}
+function invalidTypeError(name: string, expectedType: string): Error {
+ return new Error(`${name} class doesn't have @${expectedType} decorator or is missing metadata.`);
+}
+
class R3TestCompiler implements Compiler {
constructor(private testBed: R3TestBedCompiler) {}