In-depth Guides
Testing

Basics of testing components

A component, unlike all other parts of an Angular application, combines an HTML template and a TypeScript class. The component truly is the template and the class working together. To adequately test a component, you should test that they work together as intended.

Such tests require creating the component's host element in the browser DOM, as Angular does, and investigating the component class's interaction with the DOM as described by its template.

The Angular TestBed facilitates this kind of testing as you'll see in the following sections. But in many cases, testing the component class alone, without DOM involvement, can validate much of the component's behavior in a straightforward, more obvious way.

Component class testing

Test a component class on its own as you would test a service class.

Component class testing should be kept very clean and simple. It should test only a single unit. At first glance, you should be able to understand what the test is testing.

Consider this LightswitchComponent which toggles a light on and off (represented by an on-screen message) when the user clicks the button.

app/demo/demo.ts (LightswitchComp)

      
import {
Component,
ContentChildren,
Directive,
EventEmitter,
HostBinding,
HostListener,
Injectable,
Input,
OnChanges,
OnDestroy,
OnInit,
Optional,
Output,
Pipe,
PipeTransform,
SimpleChanges,
} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {of} from 'rxjs';
import {delay} from 'rxjs/operators';
import {sharedImports} from '../shared/shared';
////////// The App: Services and Components for the tests. //////////////
export interface Hero {
name: string;
}
////////// Services ///////////////
@Injectable()
export class ValueService {
value = 'real value';
getValue() {
return this.value;
}
setValue(value: string) {
this.value = value;
}
getObservableValue() {
return of('observable value');
}
getPromiseValue() {
return Promise.resolve('promise value');
}
getObservableDelayValue() {
return of('observable delay value').pipe(delay(10));
}
}
@Injectable()
export class MasterService {
constructor(private valueService: ValueService) {}
getValue() {
return this.valueService.getValue();
}
}
/////////// Pipe ////////////////
/*
* Reverse the input string.
*/
@Pipe({name: 'reverse', standalone: true})
export class ReversePipe implements PipeTransform {
transform(s: string) {
let r = '';
for (let i = s.length; i; ) {
r += s[--i];
}
return r;
}
}
//////////// Components /////////////
@Component({
standalone: true,
selector: 'bank-account',
template: ` Bank Name: {{ bank }} Account Id: {{ id }} `,
})
export class BankAccountComponent {
@Input() bank = '';
@Input('account') id = '';
// Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future?
// constructor(private renderer: Renderer, private el: ElementRef ) {
// renderer.setElementProperty(el.nativeElement, 'customProperty', true);
// }
}
/** A component with attributes, styles, classes, and property setting */
@Component({
standalone: true,
selector: 'bank-account-parent',
template: `
<bank-account
bank="RBC"
account="4747"
[style.width.px]="width"
[style.color]="color"
[class.closed]="isClosed"
[class.open]="!isClosed"
>
</bank-account>
`,
imports: [BankAccountComponent],
})
export class BankAccountParentComponent {
width = 200;
color = 'red';
isClosed = true;
}
@Component({
standalone: true,
selector: 'lightswitch-comp',
template: ` <button type="button" (click)="clicked()">Click me!</button>
<span>{{ message }}</span>`,
})
export class LightswitchComponent {
isOn = false;
clicked() {
this.isOn = !this.isOn;
}
get message() {
return `The light is ${this.isOn ? 'On' : 'Off'}`;
}
}
@Component({
standalone: true,
selector: 'child-1',
template: '<span>Child-1({{text}})</span>',
})
export class Child1Component {
@Input() text = 'Original';
}
@Component({
standalone: true,
selector: 'child-2',
template: '<div>Child-2({{text}})</div>',
})
export class Child2Component {
@Input() text = '';
}
@Component({
standalone: true,
selector: 'child-3',
template: '<div>Child-3({{text}})</div>',
})
export class Child3Component {
@Input() text = '';
}
@Component({
standalone: true,
selector: 'input-comp',
template: '<input [(ngModel)]="name">',
imports: [FormsModule],
})
export class InputComponent {
name = 'John';
}
/* Prefer this metadata syntax */
// @Directive({
// selector: 'input[value]',
// host: {
// '[value]': 'value',
// '(input)': 'valueChange.emit($event.target.value)'
// },
// inputs: ['value'],
// outputs: ['valueChange']
// })
// export class InputValueBinderDirective {
// value: any;
// valueChange: EventEmitter<any> = new EventEmitter();
// }
// As the styleguide recommends
@Directive({standalone: true, selector: 'input[value]'})
export class InputValueBinderDirective {
@HostBinding() @Input() value: any;
@Output() valueChange: EventEmitter<any> = new EventEmitter();
@HostListener('input', ['$event.target.value'])
onInput(value: any) {
this.valueChange.emit(value);
}
}
@Component({
standalone: true,
selector: 'input-value-comp',
template: ` Name: <input [value]="name" /> {{ name }} `,
})
export class InputValueBinderComponent {
name = 'Sally'; // initial value
}
@Component({
standalone: true,
selector: 'parent-comp',
imports: [Child1Component],
template: 'Parent(<child-1></child-1>)',
})
export class ParentComponent {}
@Component({
standalone: true,
selector: 'io-comp',
template: '<button type="button" class="hero" (click)="click()">Original {{hero.name}}</button>',
})
export class IoComponent {
@Input() hero!: Hero;
@Output() selected = new EventEmitter<Hero>();
click() {
this.selected.emit(this.hero);
}
}
@Component({
standalone: true,
selector: 'io-parent-comp',
template: `
@if (!selectedHero) {
<p><i>Click to select a hero</i></p>
}
@if (selectedHero) {
<p>The selected hero is {{ selectedHero.name }}</p>
}
@for (hero of heroes; track hero) {
<io-comp [hero]="hero" (selected)="onSelect($event)"> </io-comp>
}
`,
imports: [IoComponent, sharedImports],
})
export class IoParentComponent {
heroes: Hero[] = [{name: 'Bob'}, {name: 'Carol'}, {name: 'Ted'}, {name: 'Alice'}];
selectedHero!: Hero;
onSelect(hero: Hero) {
this.selectedHero = hero;
}
}
@Component({
standalone: true,
selector: 'my-if-comp',
template: 'MyIf(@if (showMore) {<span>More</span>})',
imports: [sharedImports],
})
export class MyIfComponent {
showMore = false;
}
@Component({
standalone: true,
selector: 'my-service-comp',
template: 'injected value: {{valueService.value}}',
providers: [ValueService],
})
export class TestProvidersComponent {
constructor(public valueService: ValueService) {}
}
@Component({
standalone: true,
selector: 'my-service-comp',
template: 'injected value: {{valueService.value}}',
viewProviders: [ValueService],
})
export class TestViewProvidersComponent {
constructor(public valueService: ValueService) {}
}
@Component({
standalone: true,
selector: 'external-template-comp',
templateUrl: './demo-external-template.html',
})
export class ExternalTemplateComponent implements OnInit {
serviceValue = '';
constructor(@Optional() private service?: ValueService) {}
ngOnInit() {
if (this.service) {
this.serviceValue = this.service.getValue();
}
}
}
@Component({
standalone: true,
selector: 'comp-w-ext-comp',
imports: [ExternalTemplateComponent],
template: `
<h3>comp-w-ext-comp</h3>
<external-template-comp></external-template-comp>
`,
})
export class InnerCompWithExternalTemplateComponent {}
@Component({standalone: true, selector: 'needs-content', template: '<ng-content></ng-content>'})
export class NeedsContentComponent {
// children with #content local variable
@ContentChildren('content') children: any;
}
///////// MyIfChildComp ////////
@Component({
standalone: true,
selector: 'my-if-child-1',
template: ` <h4>MyIfChildComp</h4>
<div>
<label for="child-value"
>Child value: <input id="child-value" [(ngModel)]="childValue" />
</label>
</div>
<p><i>Change log:</i></p>
@for (log of changeLog; track log; let i = $index) {
<div>{{ i + 1 }} - {{ log }}</div>
}`,
imports: [FormsModule, sharedImports],
})
export class MyIfChildComponent implements OnInit, OnChanges, OnDestroy {
@Input() value = '';
@Output() valueChange = new EventEmitter<string>();
get childValue() {
return this.value;
}
set childValue(v: string) {
if (this.value === v) {
return;
}
this.value = v;
this.valueChange.emit(v);
}
changeLog: string[] = [];
ngOnInitCalled = false;
ngOnChangesCounter = 0;
ngOnDestroyCalled = false;
ngOnInit() {
this.ngOnInitCalled = true;
this.changeLog.push('ngOnInit called');
}
ngOnDestroy() {
this.ngOnDestroyCalled = true;
this.changeLog.push('ngOnDestroy called');
}
ngOnChanges(changes: SimpleChanges) {
for (const propName in changes) {
this.ngOnChangesCounter += 1;
const prop = changes[propName];
const cur = JSON.stringify(prop.currentValue);
const prev = JSON.stringify(prop.previousValue);
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}
}
///////// MyIfParentComp ////////
@Component({
standalone: true,
selector: 'my-if-parent-comp',
template: `
<h3>MyIfParentComp</h3>
<label for="parent"
>Parent value:
<input id="parent" [(ngModel)]="parentValue" />
</label>
<button type="button" (click)="clicked()">{{ toggleLabel }} Child</button><br />
@if (showChild) {
<div style="margin: 4px; padding: 4px; background-color: aliceblue;">
<my-if-child-1 [(value)]="parentValue"></my-if-child-1>
</div>
}
`,
imports: [FormsModule, MyIfChildComponent, sharedImports],
})
export class MyIfParentComponent implements OnInit {
ngOnInitCalled = false;
parentValue = 'Hello, World';
showChild = false;
toggleLabel = 'Unknown';
ngOnInit() {
this.ngOnInitCalled = true;
this.clicked();
}
clicked() {
this.showChild = !this.showChild;
this.toggleLabel = this.showChild ? 'Close' : 'Show';
}
}
@Component({
standalone: true,
selector: 'reverse-pipe-comp',
template: `
<input [(ngModel)]="text" />
<span>{{ text | reverse }}</span>
`,
imports: [ReversePipe, FormsModule],
})
export class ReversePipeComponent {
text = 'my dog has fleas.';
}
@Component({
standalone: true,
imports: [NeedsContentComponent],
template: '<div>Replace Me</div>',
})
export class ShellComponent {}
@Component({
standalone: true,
selector: 'demo-comp',
template: `
<h1>Specs Demo</h1>
<my-if-parent-comp></my-if-parent-comp>
<hr />
<h3>Input/Output Component</h3>
<io-parent-comp></io-parent-comp>
<hr />
<h3>External Template Component</h3>
<external-template-comp></external-template-comp>
<hr />
<h3>Component With External Template Component</h3>
<comp-w-ext-comp></comp-w-ext-comp>
<hr />
<h3>Reverse Pipe</h3>
<reverse-pipe-comp></reverse-pipe-comp>
<hr />
<h3>InputValueBinder Directive</h3>
<input-value-comp></input-value-comp>
<hr />
<h3>Button Component</h3>
<lightswitch-comp></lightswitch-comp>
<hr />
<h3>Needs Content</h3>
<needs-content #nc>
<child-1 #content text="My"></child-1>
<child-2 #content text="dog"></child-2>
<child-2 text="has"></child-2>
<child-3 #content text="fleas"></child-3>
<div #content>!</div>
</needs-content>
`,
imports: [
Child1Component,
Child2Component,
Child3Component,
ExternalTemplateComponent,
InnerCompWithExternalTemplateComponent,
InputValueBinderComponent,
IoParentComponent,
LightswitchComponent,
NeedsContentComponent,
ReversePipeComponent,
MyIfParentComponent,
],
})
export class DemoComponent {}
//////// Aggregations ////////////
export const demoProviders = [MasterService, ValueService];

You might decide only to test that the clicked() method toggles the light's on/off state and sets the message appropriately.

This component class has no dependencies. To test these types of classes, follow the same steps as you would for a service that has no dependencies:

  1. Create a component using the new keyword.
  2. Poke at its API.
  3. Assert expectations on its public state.

app/demo/demo.spec.ts (Lightswitch tests)

      
import {LightswitchComponent, MasterService, ValueService, ReversePipe} from './demo';
///////// Fakes /////////
export class FakeValueService extends ValueService {
override value = 'faked service value';
}
////////////////////////
describe('demo (no TestBed):', () => {
// Straight Jasmine testing without Angular's testing support
describe('ValueService', () => {
let service: ValueService;
beforeEach(() => {
service = new ValueService();
});
it('#getValue should return real value', () => {
expect(service.getValue()).toBe('real value');
});
it('#getObservableValue should return value from observable', (done: DoneFn) => {
service.getObservableValue().subscribe((value) => {
expect(value).toBe('observable value');
done();
});
});
it('#getPromiseValue should return value from a promise', (done: DoneFn) => {
service.getPromiseValue().then((value) => {
expect(value).toBe('promise value');
done();
});
});
});
// MasterService requires injection of a ValueService
describe('MasterService without Angular testing support', () => {
let masterService: MasterService;
it('#getValue should return real value from the real service', () => {
masterService = new MasterService(new ValueService());
expect(masterService.getValue()).toBe('real value');
});
it('#getValue should return faked value from a fakeService', () => {
masterService = new MasterService(new FakeValueService());
expect(masterService.getValue()).toBe('faked service value');
});
it('#getValue should return faked value from a fake object', () => {
const fake = {getValue: () => 'fake value'};
masterService = new MasterService(fake as ValueService);
expect(masterService.getValue()).toBe('fake value');
});
it('#getValue should return stubbed value from a spy', () => {
// create `getValue` spy on an object representing the ValueService
const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);
// set the value to return when the `getValue` spy is called.
const stubValue = 'stub value';
valueServiceSpy.getValue.and.returnValue(stubValue);
masterService = new MasterService(valueServiceSpy);
expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);
expect(valueServiceSpy.getValue.calls.count())
.withContext('spy method was called once')
.toBe(1);
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);
});
});
describe('MasterService (no beforeEach)', () => {
it('#getValue should return stubbed value from a spy', () => {
const {masterService, stubValue, valueServiceSpy} = setup();
expect(masterService.getValue()).withContext('service returned stub value').toBe(stubValue);
expect(valueServiceSpy.getValue.calls.count())
.withContext('spy method was called once')
.toBe(1);
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue).toBe(stubValue);
});
function setup() {
const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);
const stubValue = 'stub value';
const masterService = new MasterService(valueServiceSpy);
valueServiceSpy.getValue.and.returnValue(stubValue);
return {masterService, stubValue, valueServiceSpy};
}
});
describe('ReversePipe', () => {
let pipe: ReversePipe;
beforeEach(() => {
pipe = new ReversePipe();
});
it('transforms "abc" to "cba"', () => {
expect(pipe.transform('abc')).toBe('cba');
});
it('no change to palindrome: "able was I ere I saw elba"', () => {
const palindrome = 'able was I ere I saw elba';
expect(pipe.transform(palindrome)).toBe(palindrome);
});
});
describe('LightswitchComp', () => {
it('#clicked() should toggle #isOn', () => {
const comp = new LightswitchComponent();
expect(comp.isOn).withContext('off at first').toBe(false);
comp.clicked();
expect(comp.isOn).withContext('on after click').toBe(true);
comp.clicked();
expect(comp.isOn).withContext('off after second click').toBe(false);
});
it('#clicked() should set #message to "is on"', () => {
const comp = new LightswitchComponent();
expect(comp.message)
.withContext('off at first')
.toMatch(/is off/i);
comp.clicked();
expect(comp.message).withContext('on after clicked').toMatch(/is on/i);
});
});
});

Here is the DashboardHeroComponent from the Tour of Heroes tutorial.

app/dashboard/dashboard-hero.component.ts (component)

      
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {UpperCasePipe} from '@angular/common';
import {Hero} from '../model/hero';
@Component({
standalone: true,
selector: 'dashboard-hero',
template: `
<button type="button" (click)="click()" class="hero">
{{ hero.name | uppercase }}
</button>
`,
styleUrls: ['./dashboard-hero.component.css'],
imports: [UpperCasePipe],
})
export class DashboardHeroComponent {
@Input() hero!: Hero;
@Output() selected = new EventEmitter<Hero>();
click() {
this.selected.emit(this.hero);
}
}

It appears within the template of a parent component, which binds a hero to the @Input property and listens for an event raised through the selected @Output property.

You can test that the class code works without creating the DashboardHeroComponent or its parent component.

app/dashboard/dashboard-hero.component.spec.ts (class tests)

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {first} from 'rxjs/operators';
import {addMatchers, click} from '../../testing';
import {appProviders} from '../app.config';
import {Hero} from '../model/hero';
import {DashboardHeroComponent} from './dashboard-hero.component';
beforeEach(addMatchers);
describe('DashboardHeroComponent class only', () => {
it('raises the selected event when clicked', () => {
const comp = new DashboardHeroComponent();
const hero: Hero = {id: 42, name: 'Test'};
comp.hero = hero;
comp.selected.pipe(first()).subscribe((selectedHero: Hero) => expect(selectedHero).toBe(hero));
comp.click();
});
});
describe('DashboardHeroComponent when tested directly', () => {
let comp: DashboardHeroComponent;
let expectedHero: Hero;
let fixture: ComponentFixture<DashboardHeroComponent>;
let heroDe: DebugElement;
let heroEl: HTMLElement;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
providers: appProviders,
imports: [DashboardHeroComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DashboardHeroComponent);
comp = fixture.componentInstance;
// find the hero's DebugElement and element
heroDe = fixture.debugElement.query(By.css('.hero'));
heroEl = heroDe.nativeElement;
// mock the hero supplied by the parent component
expectedHero = {id: 42, name: 'Test Name'};
// simulate the parent setting the input property with that hero
comp.hero = expectedHero;
// trigger initial data binding
fixture.detectChanges();
});
it('should display hero name in uppercase', () => {
const expectedPipedName = expectedHero.name.toUpperCase();
expect(heroEl.textContent).toContain(expectedPipedName);
});
it('should raise selected event when clicked (triggerEventHandler)', () => {
let selectedHero: Hero | undefined;
comp.selected.pipe(first()).subscribe((hero: Hero) => (selectedHero = hero));
heroDe.triggerEventHandler('click');
expect(selectedHero).toBe(expectedHero);
});
it('should raise selected event when clicked (element.click)', () => {
let selectedHero: Hero | undefined;
comp.selected.pipe(first()).subscribe((hero: Hero) => (selectedHero = hero));
heroEl.click();
expect(selectedHero).toBe(expectedHero);
});
it('should raise selected event when clicked (click helper with DebugElement)', () => {
let selectedHero: Hero | undefined;
comp.selected.pipe(first()).subscribe((hero: Hero) => (selectedHero = hero));
click(heroDe); // click helper with DebugElement
expect(selectedHero).toBe(expectedHero);
});
it('should raise selected event when clicked (click helper with native element)', () => {
let selectedHero: Hero | undefined;
comp.selected.pipe(first()).subscribe((hero: Hero) => (selectedHero = hero));
click(heroEl); // click helper with native element
expect(selectedHero).toBe(expectedHero);
});
});
//////////////////
describe('DashboardHeroComponent when inside a test host', () => {
let testHost: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
let heroEl: HTMLElement;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
providers: appProviders,
imports: [DashboardHeroComponent, TestHostComponent],
})
.compileComponents();
}));
beforeEach(() => {
// create TestHostComponent instead of DashboardHeroComponent
fixture = TestBed.createComponent(TestHostComponent);
testHost = fixture.componentInstance;
heroEl = fixture.nativeElement.querySelector('.hero');
fixture.detectChanges(); // trigger initial data binding
});
it('should display hero name', () => {
const expectedPipedName = testHost.hero.name.toUpperCase();
expect(heroEl.textContent).toContain(expectedPipedName);
});
it('should raise selected event when clicked', () => {
click(heroEl);
// selected hero should be the same data bound hero
expect(testHost.selectedHero).toBe(testHost.hero);
});
});
////// Test Host Component //////
import {Component} from '@angular/core';
@Component({
standalone: true,
imports: [DashboardHeroComponent],
template: ` <dashboard-hero [hero]="hero" (selected)="onSelected($event)"> </dashboard-hero>`,
})
class TestHostComponent {
hero: Hero = {id: 42, name: 'Test Name'};
selectedHero: Hero | undefined;
onSelected(hero: Hero) {
this.selectedHero = hero;
}
}

When a component has dependencies, you might want to use the TestBed to both create the component and its dependencies.

The following WelcomeComponent depends on the UserService to know the name of the user to greet.

IMPORTANT: Remember to either import or provide each standalone component you want to test.

app/welcome/welcome.component.ts

      
import {Component, OnInit} from '@angular/core';
import {UserService} from '../model/user.service';
@Component({
standalone: true,
selector: 'app-welcome',
template: '<h3 class="welcome"><i>{{welcome}}</i></h3>',
})
export class WelcomeComponent implements OnInit {
welcome = '';
constructor(private userService: UserService) {}
ngOnInit(): void {
this.welcome = this.userService.isLoggedIn
? 'Welcome, ' + this.userService.user.name
: 'Please log in.';
}
}

You might start by creating a mock of the UserService that meets the minimum needs of this component.

app/welcome/welcome.component.spec.ts (MockUserService)

      
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {UserService} from '../model/user.service';
import {WelcomeComponent} from './welcome.component';
class MockUserService {
isLoggedIn = true;
user = {name: 'Test User'};
}
describe('WelcomeComponent (class only)', () => {
let comp: WelcomeComponent;
let userService: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
// provide the component-under-test and dependent service
providers: [WelcomeComponent, {provide: UserService, useClass: MockUserService}],
});
// inject both the component and the dependent service.
comp = TestBed.inject(WelcomeComponent);
userService = TestBed.inject(UserService);
});
it('should not have welcome message after construction', () => {
expect(comp.welcome).toBe('');
});
it('should welcome logged in user after Angular calls ngOnInit', () => {
comp.ngOnInit();
expect(comp.welcome).toContain(userService.user.name);
});
it('should ask user to log in if not logged in after ngOnInit', () => {
userService.isLoggedIn = false;
comp.ngOnInit();
expect(comp.welcome).not.toContain(userService.user.name);
expect(comp.welcome).toContain('log in');
});
});
describe('WelcomeComponent', () => {
let comp: WelcomeComponent;
let fixture: ComponentFixture<WelcomeComponent>;
let componentUserService: UserService; // the actually injected service
let userService: UserService; // the TestBed injected service
let el: HTMLElement; // the DOM element with the welcome message
let userServiceStub: Partial<UserService>;
beforeEach(() => {
// stub UserService for test purposes
userServiceStub = {
isLoggedIn: true,
user: {name: 'Test User'},
};
TestBed.configureTestingModule({
imports: [WelcomeComponent],
// providers: [ UserService ], // NO! Don't provide the real service!
// Provide a test-double instead
providers: [{provide: UserService, useValue: userServiceStub}],
});
fixture = TestBed.createComponent(WelcomeComponent);
comp = fixture.componentInstance;
// UserService actually injected into the component
userService = fixture.debugElement.injector.get(UserService);
componentUserService = userService;
// UserService from the root injector
userService = TestBed.inject(UserService);
// get the "welcome" element by CSS selector (e.g., by class name)
el = fixture.nativeElement.querySelector('.welcome');
});
it('should welcome the user', () => {
fixture.detectChanges();
const content = el.textContent;
expect(content).withContext('"Welcome ..."').toContain('Welcome');
expect(content).withContext('expected name').toContain('Test User');
});
it('should welcome "Bubba"', () => {
userService.user.name = 'Bubba'; // welcome message hasn't been shown yet
fixture.detectChanges();
expect(el.textContent).toContain('Bubba');
});
it('should request login if not logged in', () => {
userService.isLoggedIn = false; // welcome message hasn't been shown yet
fixture.detectChanges();
const content = el.textContent;
expect(content).withContext('not welcomed').not.toContain('Welcome');
expect(content)
.withContext('"log in"')
.toMatch(/log in/i);
});
it("should inject the component's UserService instance", inject(
[UserService],
(service: UserService) => {
expect(service).toBe(componentUserService);
},
));
it('TestBed and Component UserService should be the same', () => {
expect(userService).toBe(componentUserService);
});
});

Then provide and inject both the component and the service in the TestBed configuration.

app/welcome/welcome.component.spec.ts (class-only setup)

      
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {UserService} from '../model/user.service';
import {WelcomeComponent} from './welcome.component';
class MockUserService {
isLoggedIn = true;
user = {name: 'Test User'};
}
describe('WelcomeComponent (class only)', () => {
let comp: WelcomeComponent;
let userService: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
// provide the component-under-test and dependent service
providers: [WelcomeComponent, {provide: UserService, useClass: MockUserService}],
});
// inject both the component and the dependent service.
comp = TestBed.inject(WelcomeComponent);
userService = TestBed.inject(UserService);
});
it('should not have welcome message after construction', () => {
expect(comp.welcome).toBe('');
});
it('should welcome logged in user after Angular calls ngOnInit', () => {
comp.ngOnInit();
expect(comp.welcome).toContain(userService.user.name);
});
it('should ask user to log in if not logged in after ngOnInit', () => {
userService.isLoggedIn = false;
comp.ngOnInit();
expect(comp.welcome).not.toContain(userService.user.name);
expect(comp.welcome).toContain('log in');
});
});
describe('WelcomeComponent', () => {
let comp: WelcomeComponent;
let fixture: ComponentFixture<WelcomeComponent>;
let componentUserService: UserService; // the actually injected service
let userService: UserService; // the TestBed injected service
let el: HTMLElement; // the DOM element with the welcome message
let userServiceStub: Partial<UserService>;
beforeEach(() => {
// stub UserService for test purposes
userServiceStub = {
isLoggedIn: true,
user: {name: 'Test User'},
};
TestBed.configureTestingModule({
imports: [WelcomeComponent],
// providers: [ UserService ], // NO! Don't provide the real service!
// Provide a test-double instead
providers: [{provide: UserService, useValue: userServiceStub}],
});
fixture = TestBed.createComponent(WelcomeComponent);
comp = fixture.componentInstance;
// UserService actually injected into the component
userService = fixture.debugElement.injector.get(UserService);
componentUserService = userService;
// UserService from the root injector
userService = TestBed.inject(UserService);
// get the "welcome" element by CSS selector (e.g., by class name)
el = fixture.nativeElement.querySelector('.welcome');
});
it('should welcome the user', () => {
fixture.detectChanges();
const content = el.textContent;
expect(content).withContext('"Welcome ..."').toContain('Welcome');
expect(content).withContext('expected name').toContain('Test User');
});
it('should welcome "Bubba"', () => {
userService.user.name = 'Bubba'; // welcome message hasn't been shown yet
fixture.detectChanges();
expect(el.textContent).toContain('Bubba');
});
it('should request login if not logged in', () => {
userService.isLoggedIn = false; // welcome message hasn't been shown yet
fixture.detectChanges();
const content = el.textContent;
expect(content).withContext('not welcomed').not.toContain('Welcome');
expect(content)
.withContext('"log in"')
.toMatch(/log in/i);
});
it("should inject the component's UserService instance", inject(
[UserService],
(service: UserService) => {
expect(service).toBe(componentUserService);
},
));
it('TestBed and Component UserService should be the same', () => {
expect(userService).toBe(componentUserService);
});
});

Then exercise the component class, remembering to call the lifecycle hook methods as Angular does when running the application.

app/welcome/welcome.component.spec.ts (class-only tests)

      
import {ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {UserService} from '../model/user.service';
import {WelcomeComponent} from './welcome.component';
class MockUserService {
isLoggedIn = true;
user = {name: 'Test User'};
}
describe('WelcomeComponent (class only)', () => {
let comp: WelcomeComponent;
let userService: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
// provide the component-under-test and dependent service
providers: [WelcomeComponent, {provide: UserService, useClass: MockUserService}],
});
// inject both the component and the dependent service.
comp = TestBed.inject(WelcomeComponent);
userService = TestBed.inject(UserService);
});
it('should not have welcome message after construction', () => {
expect(comp.welcome).toBe('');
});
it('should welcome logged in user after Angular calls ngOnInit', () => {
comp.ngOnInit();
expect(comp.welcome).toContain(userService.user.name);
});
it('should ask user to log in if not logged in after ngOnInit', () => {
userService.isLoggedIn = false;
comp.ngOnInit();
expect(comp.welcome).not.toContain(userService.user.name);
expect(comp.welcome).toContain('log in');
});
});
describe('WelcomeComponent', () => {
let comp: WelcomeComponent;
let fixture: ComponentFixture<WelcomeComponent>;
let componentUserService: UserService; // the actually injected service
let userService: UserService; // the TestBed injected service
let el: HTMLElement; // the DOM element with the welcome message
let userServiceStub: Partial<UserService>;
beforeEach(() => {
// stub UserService for test purposes
userServiceStub = {
isLoggedIn: true,
user: {name: 'Test User'},
};
TestBed.configureTestingModule({
imports: [WelcomeComponent],
// providers: [ UserService ], // NO! Don't provide the real service!
// Provide a test-double instead
providers: [{provide: UserService, useValue: userServiceStub}],
});
fixture = TestBed.createComponent(WelcomeComponent);
comp = fixture.componentInstance;
// UserService actually injected into the component
userService = fixture.debugElement.injector.get(UserService);
componentUserService = userService;
// UserService from the root injector
userService = TestBed.inject(UserService);
// get the "welcome" element by CSS selector (e.g., by class name)
el = fixture.nativeElement.querySelector('.welcome');
});
it('should welcome the user', () => {
fixture.detectChanges();
const content = el.textContent;
expect(content).withContext('"Welcome ..."').toContain('Welcome');
expect(content).withContext('expected name').toContain('Test User');
});
it('should welcome "Bubba"', () => {
userService.user.name = 'Bubba'; // welcome message hasn't been shown yet
fixture.detectChanges();
expect(el.textContent).toContain('Bubba');
});
it('should request login if not logged in', () => {
userService.isLoggedIn = false; // welcome message hasn't been shown yet
fixture.detectChanges();
const content = el.textContent;
expect(content).withContext('not welcomed').not.toContain('Welcome');
expect(content)
.withContext('"log in"')
.toMatch(/log in/i);
});
it("should inject the component's UserService instance", inject(
[UserService],
(service: UserService) => {
expect(service).toBe(componentUserService);
},
));
it('TestBed and Component UserService should be the same', () => {
expect(userService).toBe(componentUserService);
});
});

Component DOM testing

Testing the component class is as straightforward as testing a service.

But a component is more than just its class. A component interacts with the DOM and with other components. The class-only tests can tell you about class behavior. They cannot tell you if the component is going to render properly, respond to user input and gestures, or integrate with its parent and child components.

None of the preceding class-only tests can answer key questions about how the components actually behave on screen.

  • Is Lightswitch.clicked() bound to anything such that the user can invoke it?
  • Is the Lightswitch.message displayed?
  • Can the user actually select the hero displayed by DashboardHeroComponent?
  • Is the hero name displayed as expected (such as uppercase)?
  • Is the welcome message displayed by the template of WelcomeComponent?

These might not be troubling questions for the preceding simple components illustrated. But many components have complex interactions with the DOM elements described in their templates, causing HTML to appear and disappear as the component state changes.

To answer these kinds of questions, you have to create the DOM elements associated with the components, you must examine the DOM to confirm that component state displays properly at the appropriate times, and you must simulate user interaction with the screen to determine whether those interactions cause the component to behave as expected.

To write these kinds of test, you'll use additional features of the TestBed as well as other testing helpers.

CLI-generated tests

The CLI creates an initial test file for you by default when you ask it to generate a new component.

For example, the following CLI command generates a BannerComponent in the app/banner folder (with inline template and styles):

      
ng generate component banner --inline-template --inline-style --module app

It also generates an initial test file for the component, banner-external.component.spec.ts, that looks like this:

app/banner/banner-external.component.spec.ts (initial)

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

HELPFUL: Because compileComponents is asynchronous, it uses the waitForAsync utility function imported from @angular/core/testing.

Refer to the waitForAsync section for more details.

Reduce the setup

Only the last three lines of this file actually test the component and all they do is assert that Angular can create the component.

The rest of the file is boilerplate setup code anticipating more advanced tests that might become necessary if the component evolves into something substantial.

You'll learn about these advanced test features in the following sections. For now, you can radically reduce this test file to a more manageable size:

app/banner/banner-initial.component.spec.ts (minimal)

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

In this example, the metadata object passed to TestBed.configureTestingModule simply declares BannerComponent, the component to test.

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

HELPFUL: There's no need to declare or import anything else. The default test module is pre-configured with something like the BrowserModule from @angular/platform-browser.

Later you'll call TestBed.configureTestingModule() with imports, providers, and more declarations to suit your testing needs. Optional override methods can further fine-tune aspects of the configuration.

createComponent()

After configuring TestBed, you call its createComponent() method.

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

TestBed.createComponent() creates an instance of the BannerComponent, adds a corresponding element to the test-runner DOM, and returns a ComponentFixture.

IMPORTANT: Do not re-configure TestBed after calling createComponent.

The createComponent method freezes the current TestBed definition, closing it to further configuration.

You cannot call any more TestBed configuration methods, not configureTestingModule(), nor get(), nor any of the override... methods. If you try, TestBed throws an error.

ComponentFixture

The ComponentFixture is a test harness for interacting with the created component and its corresponding element.

Access the component instance through the fixture and confirm it exists with a Jasmine expectation:

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

beforeEach()

You will add more tests as this component evolves. Rather than duplicate the TestBed configuration for each test, you refactor to pull the setup into a Jasmine beforeEach() and some supporting variables:

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

Now add a test that gets the component's element from fixture.nativeElement and looks for the expected text.

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

nativeElement

The value of ComponentFixture.nativeElement has the any type. Later you'll encounter the DebugElement.nativeElement and it too has the any type.

Angular can't know at compile time what kind of HTML element the nativeElement is or if it even is an HTML element. The application might be running on a non-browser platform, such as the server or a Web Worker, where the element might have a diminished API or not exist at all.

The tests in this guide are designed to run in a browser so a nativeElement value will always be an HTMLElement or one of its derived classes.

Knowing that it is an HTMLElement of some sort, use the standard HTML querySelector to dive deeper into the element tree.

Here's another test that calls HTMLElement.querySelector to get the paragraph element and look for the banner text:

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

DebugElement

The Angular fixture provides the component's element directly through the fixture.nativeElement.

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

This is actually a convenience method, implemented as fixture.debugElement.nativeElement.

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

There's a good reason for this circuitous path to the element.

The properties of the nativeElement depend upon the runtime environment. You could be running these tests on a non-browser platform that doesn't have a DOM or whose DOM-emulation doesn't support the full HTMLElement API.

Angular relies on the DebugElement abstraction to work safely across all supported platforms. Instead of creating an HTML element tree, Angular creates a DebugElement tree that wraps the native elements for the runtime platform. The nativeElement property unwraps the DebugElement and returns the platform-specific element object.

Because the sample tests for this guide are designed to run only in a browser, a nativeElement in these tests is always an HTMLElement whose familiar methods and properties you can explore within a test.

Here's the previous test, re-implemented with fixture.debugElement.nativeElement:

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

The DebugElement has other methods and properties that are useful in tests, as you'll see elsewhere in this guide.

You import the DebugElement symbol from the Angular core library.

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

By.css()

Although the tests in this guide all run in the browser, some applications might run on a different platform at least some of the time.

For example, the component might render first on the server as part of a strategy to make the application launch faster on poorly connected devices. The server-side renderer might not support the full HTML element API. If it doesn't support querySelector, the previous test could fail.

The DebugElement offers query methods that work for all supported platforms. These query methods take a predicate function that returns true when a node in the DebugElement tree matches the selection criteria.

You create a predicate with the help of a By class imported from a library for the runtime platform. Here's the By import for the browser platform:

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

The following example re-implements the previous test with DebugElement.query() and the browser's By.css method.

      
import {DebugElement} from '@angular/core';
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BannerComponent} from './banner-initial.component';
/*
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
*/
describe('BannerComponent (initial CLI generated)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({imports: [BannerComponent]}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeDefined();
});
});
describe('BannerComponent (minimal)', () => {
it('should create', () => {
TestBed.configureTestingModule({imports: [BannerComponent]});
const fixture = TestBed.createComponent(BannerComponent);
const component = fixture.componentInstance;
expect(component).toBeDefined();
});
});
describe('BannerComponent (with beforeEach)', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(() => {
TestBed.configureTestingModule({imports: [BannerComponent]});
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeDefined();
});
it('should contain "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
expect(bannerElement.textContent).toContain('banner works!');
});
it('should have <p> with "banner works!"', () => {
const bannerElement: HTMLElement = fixture.nativeElement;
const p = bannerElement.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.nativeElement)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const bannerEl: HTMLElement = bannerDe.nativeElement;
const p = bannerEl.querySelector('p')!;
expect(p.textContent).toEqual('banner works!');
});
it('should find the <p> with fixture.debugElement.query(By.css)', () => {
const bannerDe: DebugElement = fixture.debugElement;
const paragraphDe = bannerDe.query(By.css('p'));
const p: HTMLElement = paragraphDe.nativeElement;
expect(p.textContent).toEqual('banner works!');
});
});

Some noteworthy observations:

  • The By.css() static method selects DebugElement nodes with a standard CSS selector.
  • The query returns a DebugElement for the paragraph.
  • You must unwrap that result to get the paragraph element.

When you're filtering by CSS selector and only testing properties of a browser's native element, the By.css approach might be overkill.

It's often more straightforward and clear to filter with a standard HTMLElement method such as querySelector() or querySelectorAll().