These tests require creating the component's host element in the browser DOM, and investigating the component class's interaction with the DOM as described by its template.
Angular TestBed facilitates this kind of testing, but in many cases, testing the component class alone, without DOM involvement can validate much of the component's behavior.
For tests without DOM interaction you will be:
Creating the Component with the new keyword, like in Services.
Asseting expectations on its public data and methods.
Components are more than just a Class, they interact with DOM and with other components.
Only testing a component's class cannot tell if the component is going to render properly, respond to user input and gestures, or integrate with its parent and child components.
Writing these kind of tests will require TestBed as well as other testing helpers.
When you create a Component with CLI, an initial test file is created.
The created .spec file has some boilerplate code antecipating more advanced tests.
Angular cannot know at compile time what kind of HTMLElement the nativeElement ir or if it event is an HTMLElement. (The application might be running on a non-browser plataform, such as the server or a Web Worker)
Knowing that it is an HTMLElement, you may use querySelector to dive deeper into the element tree.
createComponent does not bind data, meaning that if you try to test data from signals or other bindings it might not have the expected value.
This happens because createComponent does not trigger change detection by default.
For dealing with triggering change detection, use fixture.detectChanges().
banner.component.ts
@Component({
selector: 'app-banner',
template: '<h1>{{title()}}</h1>',
styles: ['h1 { color: green; font-size: 350%}'],
})
export class BannerComponent {
title = signal('Test Tour of Heroes');
}
banner.component.specs.ts
it('should display original title after detectChanges()', () => {
fixture.detectChanges();
expect(h1.textContent).toContain(component.title);
});
it('should display a different test title', () => {
component.title = 'Test Title';
fixture.detectChanges();
expect(h1.textContent).toContain('Test Title');
});
A provider configured in TestBed to enable automatic change detection.
import {ComponentFixtureAutoDetect} from '@angular/core/testing';
TestBed.configureTestingModule({
providers: [{provide: ComponentFixtureAutoDetect, useValue: true}],
});
it('should display original title', () => {
// Hooray! No `fixture.detectChanges()` needed
expect(h1.textContent).toContain(comp.title);
});
it('should still see original title after comp.title change', async () => {
const oldTitle = comp.title;
const newTitle = 'Test Title';
comp.title.set(newTitle);
// Displayed title is old because Angular didn't yet run change detection
expect(h1.textContent).toContain(oldTitle);
await fixture.whenStable();
expect(h1.textContent).toContain(newTitle);
});
it('should display updated title after detectChanges', () => {
comp.title.set('Test Title');
fixture.detectChanges(); // detect changes explicitly
expect(h1.textContent).toContain(comp.title);
});
The second and third test reveal an important limitation.
The Angular testing environment does not run change detection synchronously when updates happen inside the test case that changed the component's title.
The test must call await fixture.whenStable to wait for another of change detection.
Examples
Testing Components with async pipe
When testing the components, we will mock the ApiService.getFruits() since the API should not be reached in the test.