Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

A component harness is a small test class from Angular’s CDK that lets your tests drive a component through the operations a user would perform, such as clicking a button or reading a value, instead of querying its DOM directly. You create one by extending ComponentHarness, declaring a static hostSelector, and exposing user-level methods. In a TestBed test, you load it with TestbedHarnessEnvironment.loader(fixture). Harnesses pay off most for shared, interactive components. For a page component used in only one place, plain DOM queries are often simpler.

When a component deserves a harness

Angular’s “Component harnesses overview” describes a harness 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 gives three benefits. A harness can insulate consumer tests from implementation details such as DOM structure and CSS selectors. It makes tests easier to read and maintain. And it lets the same harness work across different test environments. These are the framework’s stated benefits, not measured guarantees.

Angular recommends harnesses especially for components that are shared and interactive. The strongest candidates are:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Reusable widgets used by several features or applications, such as a select, a date picker, or a data table.
  • Components in a component library, where consumers should not depend on internal markup.
  • Interactions you want to exercise in both unit tests and end-to-end tests with one API.

A page component used in only one place is a weaker candidate. Its template and its tests usually change together, so a harness adds a class to maintain without much protection in return. Querying the fixture directly is the better choice there.

Install the CDK

The harness API ships in the @angular/cdk package. The official guide’s CLI example is:

ng add @angular/cdk

Two entry points matter for testing. @angular/cdk/testing provides the base classes such as ComponentHarness and HarnessPredicate. @angular/cdk/testing/testbed provides TestbedHarnessEnvironment for TestBed tests. The official pages reviewed for this article do not pin a specific Angular or CDK version, so check the import paths and test setup against the versions in your project before copying the code.

Build a minimal harness

The examples below assume a simple counter component with a host element app-counter, an increment button with the class increment, and a value label with the class value. The complete harness is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';

export class CounterHarness extends ComponentHarness {
  static hostSelector = 'app-counter';

  private incrementButton = this.locatorFor('button.increment');
  private valueLabel = this.locatorFor('.value');

  static with(options: { value?: string } = {}): HarnessPredicate<CounterHarness> {
    return new HarnessPredicate(CounterHarness, options).addOption(
      'value',
      options.value,
      (harness, value) => HarnessPredicate.stringMatches(harness.getValue(), value)
    );
  }

  async getValue(): Promise<string> {
    return (await this.valueLabel()).text();
  }

  async increment(): Promise<void> {
    await (await this.incrementButton()).click();
  }
}

Define the host selector and private locators

The static hostSelector must match the element that Angular renders for the component or directive. If the selector is wrong, the loader will not find the harness. The locatorFor calls are where DOM knowledge belongs. Keep them private so that consumers cannot reach around the harness and depend on selectors.

Expose user-level operations

Public methods should describe what a user does or observes, such as increment() or getValue(). Avoid methods like clickIncrementButton() that leak the implementation, and avoid returning raw TestElement objects from the public API. If a test needs a selector to be meaningful, the harness is probably exposing the wrong abstraction.

Add a static with() predicate

Most harnesses should also implement a static with method that returns a HarnessPredicate. The predicate lets a loader filter matching instances, for example when a page contains several counters. The example above filters by displayed value. Predicates are evaluated asynchronously, so the match function may return a promise.

Load the harness in TestBed tests

  1. Create the component fixture: const fixture = TestBed.createComponent(CounterComponent);
  2. Create a loader from the fixture: const loader = TestbedHarnessEnvironment.loader(fixture);
  3. Query for the harness with await loader.getHarness(CounterHarness). Use getAllHarnesses when you need every match. getHarness rejects when nothing matches.
  4. Pass a predicate to narrow the match, for example await loader.getHarness(CounterHarness.with({ value: '0' })).
  5. Call harness methods and await every result, as shown in the test below.
it('increments the counter', async () => {
  const fixture = TestBed.createComponent(CounterComponent);
  const loader = TestbedHarnessEnvironment.loader(fixture);
  const counter = await loader.getHarness(CounterHarness);

  await counter.increment();

  expect(await counter.getValue()).toBe('1');
});

Every loader query and harness method returns a promise. Forgetting an await is the most common source of confusing failures in harness tests.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fixture root versus document root

The loader created with TestbedHarnessEnvironment.loader(fixture) searches inside the fixture’s root element. Content attached elsewhere, such as an overlay appended to document.body, is outside that scope. For those cases, use the document-root loader:

const overlayLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
const panel = await overlayLoader.getHarness(PanelHarness);

If the harness host is the fixture root itself, skip the loader and use TestbedHarnessEnvironment.harnessForFixture(fixture, CounterHarness). The second argument is the harness class.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choosing an environment

A harness is written once and can run in more than one environment. Angular’s guide demonstrates this with TestBed for unit tests and Selenium WebDriver for end-to-end tests. The environment determines how the harness locates and drives elements.

Environment Typical use How the loader is created Notes
TestBed harness environment Angular unit tests From a ComponentFixture: fixture loader for content under the fixture root, document-root loader for content attached elsewhere Operations are asynchronous and must be awaited.
Selenium WebDriver harness environment WebDriver-based end-to-end tests From the WebDriver client and document root The same harness classes can be reused from unit tests.
Custom HarnessEnvironment A test runner or driver that the built-in environments do not cover You subclass HarnessEnvironment and supply an environment-specific TestElement Requires implementing the element interactions yourself and mapping key codes if the runner’s codes differ from TestKey.

The useful comparison axes are test scope (unit versus browser end-to-end), root location (fixture versus document), the driver’s interaction model, and whether you need a custom TestElement and HarnessEnvironment. For most Angular projects, the first two rows are sufficient.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Writing a custom environment

Write a custom environment only when your test runner cannot use the built-in ones. You need to provide an environment-specific TestElement, which implements the element operations, and a subclass of HarnessEnvironment. Angular’s operations on TestElement are asynchronous because some drivers cannot interact with DOM elements synchronously, so your implementation must follow the same promise-based contract. If the runner’s key codes differ from TestKey, map them in your implementation so that key-press operations behave the same way.

Common mistakes

  • Using a fixture loader for an overlay or dialog. The harness will not be found, so switch to documentRootLoader(fixture).
  • Setting hostSelector to a selector that does not match the rendered component element.
  • Making locators public, or returning raw TestElement objects, which exposes the DOM details the harness was meant to hide.
  • Omitting await on loader queries or harness methods.
  • Creating a harness for a single-use page component, which adds maintenance without isolating anything useful.

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.