angular-cn/packages/platform-browser/test/testing_public_spec.ts
Adrian Rutkowski c8f056beb6 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
2020-09-22 15:03:44 -07:00

1128 lines
41 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompilerConfig, ResourceLoader} from '@angular/compiler';
import {Compiler, Component, ComponentFactoryResolver, CUSTOM_ELEMENTS_SCHEMA, Directive, Inject, Injectable, InjectionToken, Injector, Input, NgModule, Optional, Pipe, SkipSelf, ɵstringify as stringify} from '@angular/core';
import {fakeAsync, getTestBed, inject, TestBed, tick, waitForAsync, withModule} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ivyEnabled, modifiedInIvy, obsoleteInIvy, onlyInIvy} from '@angular/private/testing';
// Services, and components for the tests.
@Component({selector: 'child-comp', template: `<span>Original {{childBinding}}</span>`})
@Injectable()
class ChildComp {
childBinding: string;
constructor() {
this.childBinding = 'Child';
}
}
@Component({selector: 'child-comp', template: `<span>Mock</span>`})
@Injectable()
class MockChildComp {
}
@Component({
selector: 'parent-comp',
template: `Parent(<child-comp></child-comp>)`,
})
@Injectable()
class ParentComp {
}
@Component({selector: 'my-if-comp', template: `MyIf(<span *ngIf="showMore">More</span>)`})
@Injectable()
class MyIfComp {
showMore: boolean = false;
}
@Component({selector: 'child-child-comp', template: `<span>ChildChild</span>`})
@Injectable()
class ChildChildComp {
}
@Component({
selector: 'child-comp',
template: `<span>Original {{childBinding}}(<child-child-comp></child-child-comp>)</span>`,
})
@Injectable()
class ChildWithChildComp {
childBinding: string;
constructor() {
this.childBinding = 'Child';
}
}
class FancyService {
value: string = 'real value';
getAsyncValue() {
return Promise.resolve('async value');
}
getTimeoutValue() {
return new Promise<string>((resolve, reject) => setTimeout(() => resolve('timeout value'), 10));
}
}
class MockFancyService extends FancyService {
value: string = 'mocked out value';
}
@Component({
selector: 'my-service-comp',
providers: [FancyService],
template: `injected value: {{fancyService.value}}`
})
class TestProvidersComp {
constructor(private fancyService: FancyService) {}
}
@Component({
selector: 'my-service-comp',
viewProviders: [FancyService],
template: `injected value: {{fancyService.value}}`
})
class TestViewProvidersComp {
constructor(private fancyService: FancyService) {}
}
@Directive({selector: '[someDir]', host: {'[title]': 'someDir'}})
class SomeDirective {
// TODO(issue/24571): remove '!'.
@Input() someDir!: string;
}
@Pipe({name: 'somePipe'})
class SomePipe {
transform(value: string) {
return `transformed ${value}`;
}
}
@Component({selector: 'comp', template: `<div [someDir]="'someValue' | somePipe"></div>`})
class CompUsingModuleDirectiveAndPipe {
}
@NgModule()
class SomeLibModule {
}
@Component({
selector: 'comp',
templateUrl: '/base/angular/packages/platform-browser/test/static_assets/test.html'
})
class CompWithUrlTemplate {
}
const aTok = new InjectionToken<string>('a');
const bTok = new InjectionToken<string>('b');
{
describe('public testing API', () => {
describe('using the async helper with context passing', () => {
type TestContext = {actuallyDone: boolean};
beforeEach(function(this: TestContext) {
this.actuallyDone = false;
});
afterEach(function(this: TestContext) {
expect(this.actuallyDone).toEqual(true);
});
it('should run normal tests', function(this: TestContext) {
this.actuallyDone = true;
});
it('should run normal async tests', function(this: TestContext, done) {
setTimeout(() => {
this.actuallyDone = true;
done();
}, 0);
});
it('should run async tests with tasks', waitForAsync(function(this: TestContext) {
setTimeout(() => this.actuallyDone = true, 0);
}));
it('should run async tests with promises', waitForAsync(function(this: TestContext) {
const p = new Promise((resolve, reject) => setTimeout(resolve, 10));
p.then(() => this.actuallyDone = true);
}));
});
describe('basic context passing to inject, fakeAsync and withModule helpers', () => {
const moduleConfig = {
providers: [FancyService],
};
type TestContext = {contextModified: boolean};
beforeEach(function(this: TestContext) {
this.contextModified = false;
});
afterEach(function(this: TestContext) {
expect(this.contextModified).toEqual(true);
});
it('should pass context to inject helper', inject([], function(this: TestContext) {
this.contextModified = true;
}));
it('should pass context to fakeAsync helper', fakeAsync(function(this: TestContext) {
this.contextModified = true;
}));
it('should pass context to withModule helper - simple',
withModule(moduleConfig, function(this: TestContext) {
this.contextModified = true;
}));
it('should pass context to withModule helper - advanced',
withModule(moduleConfig)
.inject([FancyService], function(this: TestContext, service: FancyService) {
expect(service.value).toBe('real value');
this.contextModified = true;
}));
it('should preserve context when async and inject helpers are combined',
waitForAsync(inject([], function(this: TestContext) {
setTimeout(() => this.contextModified = true, 0);
})));
it('should preserve context when fakeAsync and inject helpers are combined',
fakeAsync(inject([], function(this: TestContext) {
setTimeout(() => this.contextModified = true, 0);
tick(1);
})));
});
describe('using the test injector with the inject helper', () => {
describe('setting up Providers', () => {
beforeEach(() => {
TestBed.configureTestingModule(
{providers: [{provide: FancyService, useValue: new FancyService()}]});
it('should use set up providers', inject([FancyService], (service: FancyService) => {
expect(service.value).toEqual('real value');
}));
it('should wait until returned promises',
waitForAsync(inject([FancyService], (service: FancyService) => {
service.getAsyncValue().then((value) => expect(value).toEqual('async value'));
service.getTimeoutValue().then((value) => expect(value).toEqual('timeout value'));
})));
it('should allow the use of fakeAsync',
fakeAsync(inject([FancyService], (service: FancyService) => {
let value: string = undefined!;
service.getAsyncValue().then((val) => value = val);
tick();
expect(value).toEqual('async value');
})));
it('should allow use of "done"', (done) => {
inject([FancyService], (service: FancyService) => {
let count = 0;
const id = setInterval(() => {
count++;
if (count > 2) {
clearInterval(id);
done();
}
}, 5);
})(); // inject needs to be invoked explicitly with ().
});
describe('using beforeEach', () => {
beforeEach(inject([FancyService], (service: FancyService) => {
service.value = 'value modified in beforeEach';
}));
it('should use modified providers', inject([FancyService], (service: FancyService) => {
expect(service.value).toEqual('value modified in beforeEach');
}));
});
describe('using async beforeEach', () => {
beforeEach(waitForAsync(inject([FancyService], (service: FancyService) => {
service.getAsyncValue().then((value) => service.value = value);
})));
it('should use asynchronously modified value',
inject([FancyService], (service: FancyService) => {
expect(service.value).toEqual('async value');
}));
});
});
});
});
describe('using the test injector with modules', () => {
const moduleConfig = {
providers: [FancyService],
imports: [SomeLibModule],
declarations: [SomeDirective, SomePipe, CompUsingModuleDirectiveAndPipe],
};
describe('setting up a module', () => {
beforeEach(() => TestBed.configureTestingModule(moduleConfig));
it('should use set up providers', inject([FancyService], (service: FancyService) => {
expect(service.value).toEqual('real value');
}));
it('should be able to create any declared components', () => {
const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe);
expect(compFixture.componentInstance).toBeAnInstanceOf(CompUsingModuleDirectiveAndPipe);
});
it('should use set up directives and pipes', () => {
const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe);
const el = compFixture.debugElement;
compFixture.detectChanges();
expect(el.children[0].properties['title']).toBe('transformed someValue');
});
it('should use set up imported modules',
inject([SomeLibModule], (libModule: SomeLibModule) => {
expect(libModule).toBeAnInstanceOf(SomeLibModule);
}));
describe('provided schemas', () => {
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty {
}
beforeEach(() => {
TestBed.configureTestingModule(
{schemas: [CUSTOM_ELEMENTS_SCHEMA], declarations: [ComponentUsingInvalidProperty]});
});
it('should not error on unknown bound properties on custom elements when using the CUSTOM_ELEMENTS_SCHEMA',
() => {
expect(TestBed.createComponent(ComponentUsingInvalidProperty).componentInstance)
.toBeAnInstanceOf(ComponentUsingInvalidProperty);
});
});
});
describe('per test modules', () => {
it('should use set up providers',
withModule(moduleConfig).inject([FancyService], (service: FancyService) => {
expect(service.value).toEqual('real value');
}));
it('should use set up directives and pipes', withModule(moduleConfig, () => {
const compFixture = TestBed.createComponent(CompUsingModuleDirectiveAndPipe);
const el = compFixture.debugElement;
compFixture.detectChanges();
expect(el.children[0].properties['title']).toBe('transformed someValue');
}));
it('should use set up library modules',
withModule(moduleConfig).inject([SomeLibModule], (libModule: SomeLibModule) => {
expect(libModule).toBeAnInstanceOf(SomeLibModule);
}));
});
describe('components with template url', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({declarations: [CompWithUrlTemplate]});
TestBed.compileComponents();
}));
isBrowser &&
it('should allow to createSync components with templateUrl after explicit async compilation',
() => {
const fixture = TestBed.createComponent(CompWithUrlTemplate);
expect(fixture.nativeElement).toHaveText('from external template');
});
});
describe('overwriting metadata', () => {
@Pipe({name: 'undefined'})
class SomePipe {
transform(value: string): string {
return `transformed ${value}`;
}
}
@Directive({selector: '[undefined]'})
class SomeDirective {
someProp = 'hello';
}
@Component({selector: 'comp', template: 'someText'})
class SomeComponent {
}
@Component({selector: 'comp', template: 'someOtherText'})
class SomeOtherComponent {
}
@NgModule({declarations: [SomeComponent, SomeDirective, SomePipe]})
class SomeModule {
}
beforeEach(() => TestBed.configureTestingModule({imports: [SomeModule]}));
describe('module', () => {
beforeEach(() => {
TestBed.overrideModule(SomeModule, {set: {declarations: [SomeOtherComponent]}});
});
it('should work', () => {
expect(TestBed.createComponent(SomeOtherComponent).componentInstance)
.toBeAnInstanceOf(SomeOtherComponent);
});
});
describe('component', () => {
beforeEach(() => {
TestBed.overrideComponent(
SomeComponent, {set: {selector: 'comp', template: 'newText'}});
});
it('should work', () => {
expect(TestBed.createComponent(SomeComponent).nativeElement).toHaveText('newText');
});
});
describe('directive', () => {
beforeEach(() => {
TestBed
.overrideComponent(
SomeComponent, {set: {selector: 'comp', template: `<div someDir></div>`}})
.overrideDirective(
SomeDirective, {set: {selector: '[someDir]', host: {'[title]': 'someProp'}}});
});
it('should work', () => {
const compFixture = TestBed.createComponent(SomeComponent);
compFixture.detectChanges();
expect(compFixture.debugElement.children[0].properties['title']).toEqual('hello');
});
});
describe('pipe', () => {
beforeEach(() => {
TestBed
.overrideComponent(
SomeComponent, {set: {selector: 'comp', template: `{{'hello' | somePipe}}`}})
.overridePipe(SomePipe, {set: {name: 'somePipe'}})
.overridePipe(SomePipe, {add: {pure: false}});
});
it('should work', () => {
const compFixture = TestBed.createComponent(SomeComponent);
compFixture.detectChanges();
expect(compFixture.nativeElement).toHaveText('transformed hello');
});
});
describe('template', () => {
let testBedSpy: any;
beforeEach(() => {
testBedSpy = spyOn(getTestBed(), 'overrideComponent').and.callThrough();
TestBed.overrideTemplate(SomeComponent, 'newText');
});
it(`should override component's template`, () => {
const fixture = TestBed.createComponent(SomeComponent);
expect(fixture.nativeElement).toHaveText('newText');
expect(testBedSpy).toHaveBeenCalledWith(SomeComponent, {
set: {template: 'newText', templateUrl: null}
});
});
});
});
describe('overriding providers', () => {
describe('in core', () => {
it('ComponentFactoryResolver', () => {
const componentFactoryMock =
jasmine.createSpyObj('componentFactory', ['resolveComponentFactory']);
TestBed.overrideProvider(ComponentFactoryResolver, {useValue: componentFactoryMock});
expect(TestBed.get(ComponentFactoryResolver)).toEqual(componentFactoryMock);
});
});
describe('in NgModules', () => {
it('should support useValue', () => {
TestBed.configureTestingModule({
providers: [
{provide: aTok, useValue: 'aValue'},
]
});
TestBed.overrideProvider(aTok, {useValue: 'mockValue'});
expect(TestBed.inject(aTok)).toBe('mockValue');
});
it('should support useFactory', () => {
TestBed.configureTestingModule({
providers: [
{provide: 'dep', useValue: 'depValue'},
{provide: aTok, useValue: 'aValue'},
]
});
TestBed.overrideProvider(
aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']});
expect(TestBed.inject(aTok)).toBe('mockA: depValue');
});
it('should support @Optional without matches', () => {
TestBed.configureTestingModule({
providers: [
{provide: aTok, useValue: 'aValue'},
]
});
TestBed.overrideProvider(
aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
expect(TestBed.inject(aTok)).toBe('mockA: null');
});
it('should support Optional with matches', () => {
TestBed.configureTestingModule({
providers: [
{provide: 'dep', useValue: 'depValue'},
{provide: aTok, useValue: 'aValue'},
]
});
TestBed.overrideProvider(
aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
expect(TestBed.inject(aTok)).toBe('mockA: depValue');
});
it('should support SkipSelf', () => {
@NgModule({
providers: [
{provide: aTok, useValue: 'aValue'},
{provide: 'dep', useValue: 'depValue'},
]
})
class MyModule {
}
TestBed.overrideProvider(
aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]});
TestBed.configureTestingModule(
{providers: [{provide: 'dep', useValue: 'parentDepValue'}]});
const compiler = TestBed.inject(Compiler);
const modFactory = compiler.compileModuleSync(MyModule);
expect(modFactory.create(getTestBed()).injector.get(aTok))
.toBe('mockA: parentDepValue');
});
it('should keep imported NgModules eager', () => {
let someModule: SomeModule|undefined;
@NgModule()
class SomeModule {
constructor() {
someModule = this;
}
}
TestBed.configureTestingModule({
providers: [
{provide: aTok, useValue: 'aValue'},
],
imports: [SomeModule]
});
TestBed.overrideProvider(aTok, {useValue: 'mockValue'});
expect(TestBed.inject(aTok)).toBe('mockValue');
expect(someModule).toBeAnInstanceOf(SomeModule);
});
describe('injecting eager providers into an eager overwritten provider', () => {
@NgModule({
providers: [
{provide: aTok, useFactory: () => 'aValue'},
{provide: bTok, useFactory: () => 'bValue'},
]
})
class MyModule {
// NgModule is eager, which makes all of its deps eager
constructor(@Inject(aTok) a: any, @Inject(bTok) b: any) {}
}
it('should inject providers that were declared before', () => {
TestBed.configureTestingModule({imports: [MyModule]});
TestBed.overrideProvider(
bTok, {useFactory: (a: string) => `mockB: ${a}`, deps: [aTok]});
expect(TestBed.inject(bTok)).toBe('mockB: aValue');
});
it('should inject providers that were declared afterwards', () => {
TestBed.configureTestingModule({imports: [MyModule]});
TestBed.overrideProvider(
aTok, {useFactory: (b: string) => `mockA: ${b}`, deps: [bTok]});
expect(TestBed.inject(aTok)).toBe('mockA: bValue');
});
});
});
describe('in Components', () => {
it('should support useValue', () => {
@Component({
template: '',
providers: [
{provide: aTok, useValue: 'aValue'},
]
})
class MComp {
}
TestBed.overrideProvider(aTok, {useValue: 'mockValue'});
const ctx =
TestBed.configureTestingModule({declarations: [MComp]}).createComponent(MComp);
expect(ctx.debugElement.injector.get(aTok)).toBe('mockValue');
});
it('should support useFactory', () => {
@Component({
template: '',
providers: [
{provide: 'dep', useValue: 'depValue'},
{provide: aTok, useValue: 'aValue'},
]
})
class MyComp {
}
TestBed.overrideProvider(
aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: ['dep']});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get(aTok)).toBe('mockA: depValue');
});
it('should support @Optional without matches', () => {
@Component({
template: '',
providers: [
{provide: aTok, useValue: 'aValue'},
]
})
class MyComp {
}
TestBed.overrideProvider(
aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get(aTok)).toBe('mockA: null');
});
it('should support Optional with matches', () => {
@Component({
template: '',
providers: [
{provide: 'dep', useValue: 'depValue'},
{provide: aTok, useValue: 'aValue'},
]
})
class MyComp {
}
TestBed.overrideProvider(
aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new Optional(), 'dep']]});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get(aTok)).toBe('mockA: depValue');
});
it('should support SkipSelf', () => {
@Directive({
selector: '[myDir]',
providers: [
{provide: aTok, useValue: 'aValue'},
{provide: 'dep', useValue: 'depValue'},
]
})
class MyDir {
}
@Component({
template: '<div myDir></div>',
providers: [
{provide: 'dep', useValue: 'parentDepValue'},
]
})
class MyComp {
}
TestBed.overrideProvider(
aTok, {useFactory: (dep: any) => `mockA: ${dep}`, deps: [[new SkipSelf(), 'dep']]});
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir]})
.createComponent(MyComp);
expect(ctx.debugElement.children[0].injector.get(aTok)).toBe('mockA: parentDepValue');
});
it('should support multiple providers in a template', () => {
@Directive({
selector: '[myDir1]',
providers: [
{provide: aTok, useValue: 'aValue1'},
]
})
class MyDir1 {
}
@Directive({
selector: '[myDir2]',
providers: [
{provide: aTok, useValue: 'aValue2'},
]
})
class MyDir2 {
}
@Component({
template: '<div myDir1></div><div myDir2></div>',
})
class MyComp {
}
TestBed.overrideProvider(aTok, {useValue: 'mockA'});
const ctx = TestBed.configureTestingModule({declarations: [MyComp, MyDir1, MyDir2]})
.createComponent(MyComp);
expect(ctx.debugElement.children[0].injector.get(aTok)).toBe('mockA');
expect(ctx.debugElement.children[1].injector.get(aTok)).toBe('mockA');
});
describe('injecting eager providers into an eager overwritten provider', () => {
@Component({
template: '',
providers: [
{provide: aTok, useFactory: () => 'aValue'},
{provide: bTok, useFactory: () => 'bValue'},
]
})
class MyComp {
// Component is eager, which makes all of its deps eager
constructor(@Inject(aTok) a: any, @Inject(bTok) b: any) {}
}
it('should inject providers that were declared before it', () => {
TestBed.overrideProvider(
bTok, {useFactory: (a: string) => `mockB: ${a}`, deps: [aTok]});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get(bTok)).toBe('mockB: aValue');
});
it('should inject providers that were declared after it', () => {
TestBed.overrideProvider(
aTok, {useFactory: (b: string) => `mockA: ${b}`, deps: [bTok]});
const ctx =
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
expect(ctx.debugElement.injector.get(aTok)).toBe('mockA: bValue');
});
});
});
it('should reset overrides when the testing modules is resetted', () => {
TestBed.overrideProvider(aTok, {useValue: 'mockValue'});
TestBed.resetTestingModule();
TestBed.configureTestingModule({providers: [{provide: aTok, useValue: 'aValue'}]});
expect(TestBed.inject(aTok)).toBe('aValue');
});
});
describe('overrideTemplateUsingTestingModule', () => {
it('should compile the template in the context of the testing module', () => {
@Component({selector: 'comp', template: 'a'})
class MyComponent {
prop = 'some prop';
}
let testDir: TestDir|undefined;
@Directive({selector: '[test]'})
class TestDir {
constructor() {
testDir = this;
}
// TODO(issue/24571): remove '!'.
@Input('test') test!: string;
}
TestBed.overrideTemplateUsingTestingModule(
MyComponent, '<div [test]="prop">Hello world!</div>');
const fixture = TestBed.configureTestingModule({declarations: [MyComponent, TestDir]})
.createComponent(MyComponent);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Hello world!');
expect(testDir).toBeAnInstanceOf(TestDir);
expect(testDir!.test).toBe('some prop');
});
it('should reset overrides when the testing module is resetted', () => {
@Component({selector: 'comp', template: 'a'})
class MyComponent {
}
TestBed.overrideTemplateUsingTestingModule(MyComponent, 'b');
const fixture = TestBed.resetTestingModule()
.configureTestingModule({declarations: [MyComponent]})
.createComponent(MyComponent);
expect(fixture.nativeElement).toHaveText('a');
});
});
describe('setting up the compiler', () => {
describe('providers', () => {
it('should use set up providers', fakeAsync(() => {
// Keeping this component inside the test is needed to make sure it's not resolved
// prior to this test, thus having ɵcmp and a reference in resource
// resolution queue. This is done to check external resoution logic in isolation by
// configuring TestBed with the necessary ResourceLoader instance.
@Component({
selector: 'comp',
templateUrl: '/base/angular/packages/platform-browser/test/static_assets/test.html'
})
class InternalCompWithUrlTemplate {
}
const resourceLoaderGet = jasmine.createSpy('resourceLoaderGet')
.and.returnValue(Promise.resolve('Hello world!'));
TestBed.configureTestingModule({declarations: [InternalCompWithUrlTemplate]});
TestBed.configureCompiler(
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
TestBed.compileComponents();
tick();
const compFixture = TestBed.createComponent(InternalCompWithUrlTemplate);
expect(compFixture.nativeElement).toHaveText('Hello world!');
}));
});
describe('useJit true', () => {
beforeEach(() => TestBed.configureCompiler({useJit: true}));
obsoleteInIvy('the Render3 compiler JiT mode is not configurable')
.it('should set the value into CompilerConfig',
inject([CompilerConfig], (config: CompilerConfig) => {
expect(config.useJit).toBe(true);
}));
});
describe('useJit false', () => {
beforeEach(() => TestBed.configureCompiler({useJit: false}));
obsoleteInIvy('the Render3 compiler JiT mode is not configurable')
.it('should set the value into CompilerConfig',
inject([CompilerConfig], (config: CompilerConfig) => {
expect(config.useJit).toBe(false);
}));
});
});
});
describe('errors', () => {
let originalJasmineIt: (description: string, func: () => void) => jasmine.Spec;
let originalJasmineBeforeEach: (beforeEachFunction: (done: DoneFn) => void) => void;
const patchJasmineIt = () => {
let resolve: (result: any) => void;
let reject: (error: any) => void;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
originalJasmineIt = jasmine.getEnv().it;
jasmine.getEnv().it = (description: string, fn: (done: DoneFn) => void): any => {
const done = <DoneFn>(() => resolve(null));
done.fail = (err) => reject(err);
fn(done);
return null;
};
return promise;
};
const restoreJasmineIt = () => jasmine.getEnv().it = originalJasmineIt;
const patchJasmineBeforeEach = () => {
let resolve: (result: any) => void;
let reject: (error: any) => void;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
originalJasmineBeforeEach = jasmine.getEnv().beforeEach;
jasmine.getEnv().beforeEach = (fn: (done: DoneFn) => void) => {
const done = <DoneFn>(() => resolve(null));
done.fail = (err) => reject(err);
fn(done);
};
return promise;
};
const restoreJasmineBeforeEach = () => jasmine.getEnv().beforeEach =
originalJasmineBeforeEach;
it('should fail when an asynchronous error is thrown', (done) => {
const itPromise = patchJasmineIt();
const barError = new Error('bar');
it('throws an async error', waitForAsync(inject([], () => setTimeout(() => {
throw barError;
}, 0))));
itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => {
expect(err).toEqual(barError);
done();
});
restoreJasmineIt();
});
it('should fail when a returned promise is rejected', (done) => {
const itPromise = patchJasmineIt();
it('should fail with an error from a promise', waitForAsync(inject([], () => {
let reject: (error: any) => void = undefined!;
const promise = new Promise((_, rej) => reject = rej);
const p = promise.then(() => expect(1).toEqual(2));
reject('baz');
return p;
})));
itPromise.then(() => done.fail('Expected test to fail, but it did not'), (err) => {
expect(err.message).toEqual('Uncaught (in promise): baz');
done();
});
restoreJasmineIt();
});
describe('components', () => {
let resourceLoaderGet: jasmine.Spy;
beforeEach(() => {
resourceLoaderGet = jasmine.createSpy('resourceLoaderGet')
.and.returnValue(Promise.resolve('Hello world!'));
TestBed.configureCompiler(
{providers: [{provide: ResourceLoader, useValue: {get: resourceLoaderGet}}]});
});
it('should report an error for declared components with templateUrl which never call TestBed.compileComponents',
() => {
const itPromise = patchJasmineIt();
@Component({
selector: 'comp',
templateUrl: '/base/angular/packages/platform-browser/test/static_assets/test.html'
})
class InlineCompWithUrlTemplate {
}
expect(
() =>
it('should fail',
withModule(
{declarations: [InlineCompWithUrlTemplate]},
() => TestBed.createComponent(InlineCompWithUrlTemplate))))
.toThrowError(
ivyEnabled ?
`Component 'InlineCompWithUrlTemplate' is not resolved:
- templateUrl: /base/angular/packages/platform-browser/test/static_assets/test.html
Did you run and wait for 'resolveComponentResources()'?` :
`This test module uses the component ${
stringify(
InlineCompWithUrlTemplate)} which is using a "templateUrl" or "styleUrls", but they were never compiled. ` +
`Please call "TestBed.compileComponents" before your test.`);
restoreJasmineIt();
});
});
modifiedInIvy(`Unknown property error thrown instead of logging a message`)
.it('should error on unknown bound properties on custom elements by default', () => {
@Component({template: '<some-element [someUnknownProp]="true"></some-element>'})
class ComponentUsingInvalidProperty {
}
const itPromise = patchJasmineIt();
expect(
() =>
it('should fail',
withModule(
{declarations: [ComponentUsingInvalidProperty]},
() => TestBed.createComponent(ComponentUsingInvalidProperty))))
.toThrowError(/Can't bind to 'someUnknownProp'/);
restoreJasmineIt();
});
onlyInIvy(`Unknown property error logged instead of throwing`)
.it('should error on unknown bound properties on custom elements by default', () => {
@Component({template: '<div [someUnknownProp]="true"></div>'})
class ComponentUsingInvalidProperty {
}
const spy = spyOn(console, 'error');
withModule({declarations: [ComponentUsingInvalidProperty]}, () => {
const fixture = TestBed.createComponent(ComponentUsingInvalidProperty);
fixture.detectChanges();
})();
expect(spy.calls.mostRecent().args[0]).toMatch(/Can't bind to 'someUnknownProp'/);
});
});
describe('creating components', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
ChildComp,
MyIfComp,
ChildChildComp,
ParentComp,
TestProvidersComp,
TestViewProvidersComp,
]
});
});
it('should instantiate a component with valid DOM', waitForAsync(() => {
const fixture = TestBed.createComponent(ChildComp);
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Original Child');
}));
it('should allow changing members of the component', waitForAsync(() => {
const componentFixture = TestBed.createComponent(MyIfComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('MyIf()');
componentFixture.componentInstance.showMore = true;
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('MyIf(More)');
}));
it('should override a template', waitForAsync(() => {
TestBed.overrideComponent(ChildComp, {set: {template: '<span>Mock</span>'}});
const componentFixture = TestBed.createComponent(ChildComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('Mock');
}));
it('should override a provider', waitForAsync(() => {
TestBed.overrideComponent(
TestProvidersComp,
{set: {providers: [{provide: FancyService, useClass: MockFancyService}]}});
const componentFixture = TestBed.createComponent(TestProvidersComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('injected value: mocked out value');
}));
it('should override a viewProvider', waitForAsync(() => {
TestBed.overrideComponent(
TestViewProvidersComp,
{set: {viewProviders: [{provide: FancyService, useClass: MockFancyService}]}});
const componentFixture = TestBed.createComponent(TestViewProvidersComp);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('injected value: mocked out value');
}));
});
describe('using alternate components', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
MockChildComp,
ParentComp,
]
});
});
it('should override component dependencies', waitForAsync(() => {
const componentFixture = TestBed.createComponent(ParentComp);
componentFixture.detectChanges();
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/);
});
});
});
}