In-depth Guides
Testing

Using component harnesses in tests

Before you start

Tip: This guide assumes you've already read the component harnesses overview guide. Read that first if you're new to using component harnesses.

CDK Installation

The Component Dev Kit (CDK) is a set of behavior primitives for building components. To use the component harnesses, first install @angular/cdk from npm. You can do this from your terminal using the Angular CLI:

      
ng add @angular/cdk

Test harness environments and loaders

You can use component test harnesses in different test environments. Angular CDK supports two built-in environments:

  • Unit tests with Angular's TestBed
  • End-to-end tests with WebDriver

Each environment provides a harness loader. The loader creates the harness instances you use throughout your tests. See below for more specific guidance on supported testing environments.

Additional testing environments require custom bindings. See the adding harness support for additional testing environments guide for more information.

Using the loader from TestbedHarnessEnvironment for unit tests

For unit tests you can create a harness loader from TestbedHarnessEnvironment. This environment uses a component fixture created by Angular's TestBed.

To create a harness loader rooted at the fixture's root element, use the loader() method:

      
const fixture = TestBed.createComponent(MyComponent);// Create a harness loader from the fixtureconst loader = TestbedHarnessEnvironment.loader(fixture);...// Use the loader to get harness instancesconst myComponentHarness = await loader.getHarness(MyComponent);

To create a harness loader for harnesses for elements that fall outside the fixture, use the documentRootLoader() method. For example, code that displays a floating element or pop-up often attaches DOM elements directly to the document body, such as the Overlay service in Angular CDK.

You can also create a harness loader directly with harnessForFixture() for a harness at that fixture's root element directly.

Using the loader from SeleniumWebDriverHarnessEnvironment for end-to-end tests

For WebDriver-based end-to-end tests you can create a harness loader with SeleniumWebDriverHarnessEnvironment.

Use the loader() method to get the harness loader instance for the current HTML document, rooted at the document's root element. This environment uses a WebDriver client.

      
let wd: webdriver.WebDriver = getMyWebDriverClient();const loader = SeleniumWebDriverHarnessEnvironment.loader(wd);...const myComponentHarness = await loader.getHarness(MyComponent);

Using a harness loader

Harness loader instances correspond to a specific DOM element and are used to create component harness instances for elements under that specific element.

To get ComponentHarness for the first instance of the element, use the getHarness() method. You get all ComponentHarness instances, use the getAllHarnesses() method.

      
// Get harness for first instance of the elementconst myComponentHarness = await loader.getHarness(MyComponent);// Get harnesses for all instances of the elementconst myComponentHarnesses = await loader.getHarnesses(MyComponent);

As an example, consider a reusable dialog-button component that opens a dialog on click. It contains the following components, each with a corresponding harness:

  • MyDialogButton (composes the MyButton and MyDialog with a convenient API)
  • MyButton (a standard button component)
  • MyDialog (a dialog appended to document.body by MyDialogButton upon click)

The following test loads harnesses for each of these components:

      
let fixture: ComponentFixture<MyDialogButton>;let loader: HarnessLoader;let rootLoader: HarnessLoader;beforeEach(() => {  fixture = TestBed.createComponent(MyDialogButton);  loader = TestbedHarnessEnvironment.loader(fixture);  rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);});it('loads harnesses', async () => {  // Load a harness for the bootstrapped component with `harnessForFixture`  dialogButtonHarness =      await TestbedHarnessEnvironment.harnessForFixture(fixture, MyDialogButtonHarness);  // The button element is inside the fixture's root element, so we use `loader`.  const buttonHarness = await loader.getHarness(MyButtonHarness);  // Click the button to open the dialog  await buttonHarness.click();  // The dialog is appended to `document.body`, outside of the fixture's root element,  // so we use `rootLoader` in this case.  const dialogHarness = await rootLoader.getHarness(MyDialogHarness);  // ... make some assertions});

Harness behavior in different environments

Harnesses may not behave exactly the same in all environments. Some differences are unavoidable between the real user interaction versus the simulated events generated in unit tests. Angular CDK makes a best effort to normalize the behavior to the extent possible.

Interacting with child elements

To interact with elements below the root element of this harness loader, use the HarnessLoader instance of a child element. For the first instance of the child element, use the getChildLoader() method. For all instances of the child element, use the getAllChildLoaders() method.

      
const myComponentHarness = await loader.getHarness(MyComponent);// Get loader for first instance of child element with '.child' selectorconst childLoader = await myComponentHarness.getLoader('.child');// Get loaders for all instances of child elements with '.child' selectorconst allChildLoaders = await myComponentHarness.getAllChildLoaders('.child');

Filtering harnesses

When a page contains multiple instances of a particular component, you may want to filter based on some property of the component to get a particular component instance. You can use a harness predicate, a class used to associate a ComponentHarness class with predicates functions that can be used to filter component instances, to do so.

When you ask a HarnessLoader for a harness, you're actually providing a HarnessQuery. A query can be one of two things:

  • A harness constructor. This just gets that harness
  • A HarnessPredicate, which gets harnesses that are filtered based on one or more conditions

HarnessPredicate does support some base filters (selector, ancestor) that work on anything that extends ComponentHarness.

      
// Example of loading a MyButtonComponentHarness with a harness predicateconst disabledButtonPredicate = new HarnessPredicate(MyButtonComponentHarness, {selector: '[disabled]'});const disabledButton = await loader.getHarness(disabledButtonPredicate);

However it's common for harnesses to implement a static with() method that accepts component-specific filtering options and returns a HarnessPredicate.

      
// Example of loading a MyButtonComponentHarness with a specific selectorconst button = await loader.getHarness(MyButtonComponentHarness.with({selector: 'btn'}))

For more details refer to the specific harness documentation since additional filtering options are specific to each harness implementation.

Using test harness APIs

While every harness defines an API specific to its corresponding component, they all share a common base class, ComponentHarness. This base class defines a static property, hostSelector, that matches the harness class to instances of the component in the DOM.

Beyond that, the API of any given harness is specific to its corresponding component; refer to the component's documentation to learn how to use a specific harness.

As an example, the following is a test for a component that uses the Angular Material slider component harness:

      
it('should get value of slider thumb', async () => {    const slider = await loader.getHarness(MatSliderHarness);    const thumb = await slider.getEndThumb();    expect(await thumb.getValue()).toBe(50);});

Interop with Angular change detection

By default, test harnesses runs Angular's change detection before reading the state of a DOM element and after interacting with a DOM element.

There may be times that you need finer-grained control over change detection in your tests. such as checking the state of a component while an async operation is pending. In these cases use the manualChangeDetection function to disable automatic handling of change detection for a block of code.

      
it('checks state while async action is in progress', async () => {  const buttonHarness = loader.getHarness(MyButtonHarness);  await manualChangeDetection(async () => {    await buttonHarness.click();    fixture.detectChanges();    // Check expectations while async click operation is in progress.    expect(isProgressSpinnerVisible()).toBe(true);    await fixture.whenStable();    // Check expectations after async click operation complete.    expect(isProgressSpinnerVisible()).toBe(false);  });});

Almost all harness methods are asynchronous and return a Promise to support the following:

  • Support for unit tests
  • Support for end-to-end tests
  • Insulate tests against changes in asynchronous behavior

The Angular team recommends using await to improve the test readability. Calling await blocks the execution of your test until the associated Promise resolves.

Occasionally, you may want to perform multiple actions simultaneously and wait until they're all done rather than performing each action sequentially. For example, read multiple properties of a single component. In these situations use the parallel function to parallelize the operations. The parallel function works similarly to Promise.all, while also optimizing change detection checks.

      
it('reads properties in parallel', async () => {  const checkboxHarness = loader.getHarness(MyCheckboxHarness);  // Read the checked and intermediate properties simultaneously.  const [checked, indeterminate] = await parallel(() => [    checkboxHarness.isChecked(),    checkboxHarness.isIndeterminate()  ]);  expect(checked).toBe(false);  expect(indeterminate).toBe(true);});