Create a harness when a component is shared, interactive, and exercised by tests in more than one place. The harness is a class that extends ComponentHarness, identifies its component with a static hostSelector, and exposes user-level actions such as “click the greet button” instead of raw DOM queries. Tests then load the harness through a loader rather than reaching into the markup. The API described here ships in the Angular CDK (@angular/cdk), and the steps below follow Angular’s current component harness documentation.
When a component deserves a harness
Angular’s guidance is that harnesses pay off most for shared components with user interaction, such as reusable widgets and component libraries. A page component used in only one place is a weaker candidate, because its tests and its implementation change together anyway. A harness can still help there if the same interaction API is needed in both unit tests and end-to-end tests.
As an Amazon Associate I earn from qualifying purchases.
A component is a strong candidate when:
- It appears in several features or is published from a component library.
- Users click, type, select, or open it, so tests need to perform actions and read results.
- The same DOM queries are repeated across many spec files.
- The same interactions must run in a TestBed unit test and in a browser test.
A component is a weak candidate when it is a one-off page with trivial markup, or when a test only checks a single static label that a plain query already reads clearly.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Why not query the DOM directly?
Direct queries tie every spec to the current template structure and CSS classes. When a designer renames a class or wraps a button in a new element, many tests break for reasons unrelated to behavior. A harness moves those selectors into one class. Consumer tests call methods that describe what a user does, so a template change usually means editing the harness once rather than every spec.
#1 Best Overall
Angular describes the idea this way: “A component harness is a class that allows tests to interact with components the way an end user does via a supported API.” The framework states the benefits as insulation from implementation details, readability, and portability across test environments. Those are qualitative benefits; Angular does not publish a measured reduction in maintenance time.
Create a minimal harness
- Install the CDK in the workspace with
ng add @angular/cdk. The harness API is part of the@angular/cdkpackage. - Create a harness file beside the component, for example
greeting.harness.ts. - Declare a class that extends
ComponentHarnessand set a statichostSelectorto the component’s selector (here,app-greeting). - Add private locators with
locatorForand expose user-level methods that use them. - Add a static
withmethod that returns aHarnessPredicate, so tests can filter instances by content. Angular says most harnesses should provide one.
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
export class GreetingHarness extends ComponentHarness {
static hostSelector = 'app-greeting';
private getButton = this.locatorFor('button');
private getMessage = this.locatorFor('p.message');
static with(options: {text?: string} = {}): HarnessPredicate<GreetingHarness> {
return new HarnessPredicate(GreetingHarness, options)
.addOption('text', options.text,
(harness, text) => HarnessPredicate.stringMatches(harness.getMessageText(), text));
}
async clickGreet(): Promise<void> {
await (await this.getButton()).click();
}
async getMessageText(): Promise<string> {
return (await this.getMessage()).text();
}
}
Keep the public surface about behavior. A method such as clickGreet() survives a markup change; a method that returns a raw element for the caller to query does not offer much over the DOM itself.
Rank #2
Load the harness in a TestBed test
In a unit test, create the fixture, build a loader from it, and then query. Loader methods such as getHarness and getAllHarnesses are asynchronous, so each call must be awaited.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallimport {TestBed} from '@angular/core/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
import {GreetingComponent} from './greeting.component';
import {GreetingHarness} from './greeting.harness';
it('shows the greeting after the button is clicked', async () => {
const fixture = TestBed.createComponent(GreetingComponent);
const loader = TestbedHarnessEnvironment.loader(fixture);
const greeting = await loader.getHarness(GreetingHarness);
await greeting.clickGreet();
expect(await greeting.getMessageText()).toBe('Hello');
});
The expected text in this example is illustrative. To pick a specific instance among several, pass the predicate instead of the class, for example loader.getHarness(GreetingHarness.with({text: 'Hello'})).
Rank #3
Find overlays and elements outside the fixture
A fixture loader only searches inside the component under test. Dialogs, menus, and snackbars that the CDK attaches to document.body are outside that root, so a fixture loader cannot see them. Choose the loader that matches where the host element lives.
| Loader | Use when | Example |
|---|---|---|
TestbedHarnessEnvironment.loader(fixture) |
The harness host is inside the fixture’s component tree. | A GreetingHarness on app-greeting rendered by the tested component. |
TestbedHarnessEnvironment.documentRootLoader(fixture) |
The host is attached outside the fixture root, such as an overlay appended to document.body. |
A harness for a dialog component that opens from a button in the tested component. |
TestbedHarnessEnvironment.harnessForFixture(fixture, HarnessType) |
The harness host is the fixture’s root element itself. | A harness for the component being tested, when that component’s own element is the host. |
const overlayLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
const dialog = await overlayLoader.getHarness(DialogHarness);
Other test environments
Angular’s guide shows one harness working across environments: the TestBed environment for unit tests and the Selenium WebDriver environment for end-to-end tests. The same harness class is reused; only the loader changes. In a WebDriver test, create the loader from the WebDriver client and the document root.
Rank #4
| Environment | Typical context | Loader setup |
|---|---|---|
| TestBed harness environment | Angular unit tests | Start from a ComponentFixture; use the fixture loader or the document-root loader depending on where the element is attached. |
| Selenium WebDriver harness environment | Browser-based end-to-end tests | Create the loader from the WebDriver client and the document root. |
Custom HarnessEnvironment |
A test runner or driver that Angular does not provide | Implement an environment-specific TestElement and subclass HarnessEnvironment. |
Writing a custom environment
You need a custom environment only when neither built-in fits your runner. The work has two parts. First, provide a TestElement implementation for your driver. Its operations are asynchronous because some drivers cannot interact with DOM elements synchronously. Second, subclass HarnessEnvironment and implement its abstract behavior. If your runner’s key codes differ from Angular’s TestKey values, map them inside your environment so that harness keyboard actions behave the same way.
Common mistakes
- Forgetting to await. Loader queries and harness methods return promises; an unawaited call makes the assertion read a stale or pending value.
- Using the fixture loader for overlays. If the element is attached to
document.body, use the document-root loader. - Mismatched host selector. If
hostSelectordoes not match the component’s selector, the loader cannot find the component. - Exposing selectors instead of behavior. Methods that only return raw elements keep tests coupled to the DOM.
- Harnessing everything. A one-off page component with a single assertion gains little from a harness.
Angular’s documentation covers these concepts and API behavior well enough for an introductory setup. It does not pin a specific Angular or CDK version, so confirm the import paths and test runner setup against the versions in your project before copying the code.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




