Skip to main content
The Angular CDK provides code for creating component test harnesses. A component harness is a class that lets a test interact with a component via a supported API. Each harness’s API interacts with a component the same way a user would.
Component harnesses are based on the PageObject pattern commonly used for integration testing.

Benefits of Component Harnesses

Easier to Read

Harnesses make tests easier to understand with straightforward APIs

More Robust

Tests are less likely to break when updating Angular Material

Why Use Harnesses?

  1. Abstraction - Tests don’t depend on component internals like DOM structure
  2. Asynchronicity - Harnesses normalize async behavior automatically
  3. Maintainability - Angular team updates harnesses, not your tests
  4. Readability - Clear, self-documenting test code

Supported Test Environments

Uses TestbedHarnessEnvironment for component testing with Angular TestBed

Getting Started

1

Import the harness environment

import {HarnessLoader} from '@angular/cdk/testing';
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';

let loader: HarnessLoader;

describe('my-component', () => {
  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [MyModule], 
      declarations: [UserProfile]
    });
    fixture = TestBed.createComponent(UserProfile);
    loader = TestbedHarnessEnvironment.loader(fixture);
  });
});
  • @angular/cdk/testing - Shared symbols for all environments
  • @angular/cdk/testing/testbed - Karma-specific symbols
  • @angular/cdk/testing/selenium-webdriver - Selenium-specific symbols
2

Load a harness

import {MatButtonHarness} from '@angular/material/button/testing';

it('should work', async () => {
  const buttons = await loader.getAllHarnesses(MatButtonHarness); 
  const firstButton = await loader.getHarness(MatButtonHarness);
});
All harness APIs are asynchronous and return Promise objects. Use async/await syntax.
3

Filter harnesses

Use with() to filter harnesses by specific criteria:
it('should work', async () => {
  // By id
  const info = await loader.getHarness(
    MatButtonHarness.with({selector: '#more-info'})
  );
  
  // By text
  const cancel = await loader.getHarness(
    MatButtonHarness.with({text: 'Cancel'})
  );
  
  // By multiple criteria
  const okButton = await loader.getHarness(
    MatButtonHarness.with({
      selector: '.confirm', 
      text: /^(Ok|Okay)$/
    })
  );
});
4

Interact with components

it('should mark confirmed when ok button clicked', async () => {
  const okButton = await loader.getHarness(
    MatButtonHarness.with({selector: '.confirm'})
  );
  
  expect(fixture.componentInstance.confirmed).toBe(false);
  expect(await okButton.isDisabled()).toBe(false);
  
  await okButton.click();
  
  expect(fixture.componentInstance.confirmed).toBe(true);
});
Harnesses automatically call change detection and wait for stability. No need for detectChanges() or whenStable()!

Loading Harnesses from Specific Sections

You can load harnesses for a sub-section of the DOM:
it('should work', async () => {
  const footerLoader = await loader.getChildLoader('.footer');
  const footerButton = await footerLoader.getHarness(MatButtonHarness);
});

Filter Options

All harnesses support these standard options:
selector
string
CSS selector that the component must match
ancestor
string
CSS selector for an ancestor element
Additional options vary by component. For example, MatButtonHarness also supports:
text
string | RegExp
Text content or regex pattern to match

Comparison: With vs Without Harnesses

it('should switch to bug report template', async () => {
  expect(fixture.debugElement.query('bug-report-form')).toBeNull();
  
  const selectTrigger = fixture.debugElement.query(
    By.css('.mat-select-trigger')
  );
  selectTrigger.triggerEventHandler('click', {});
  fixture.detectChanges();
  await fixture.whenStable();
  
  const options = document.querySelectorAll('.mat-select-panel mat-option');
  options[1].click(); // Click the second option, "Bug"
  fixture.detectChanges();
  await fixture.whenStable();
  
  expect(fixture.debugElement.query('bug-report-form')).not.toBeNull();
});
This test depends on internal DOM structure and requires manual change detection.

Key Advantages

  • await select.open() is self-explanatory
  • Filter API makes code self-documenting
  • No comments needed to explain intent
  • Eliminates repetitive detectChanges() and whenStable() calls
  • No dependency on internal CSS selectors
  • Protected from DOM restructuring
  • Normalized asynchronous behavior
  • Angular team maintains harness compatibility
  • Survives component refactoring
  • Works across async/sync changes
  • One update point for breaking changes
  • Reduces maintenance burden

Common Patterns

Testing Forms

it('should validate form', async () => {
  const input = await loader.getHarness(
    MatInputHarness.with({selector: '[name="email"]'})
  );
  
  await input.setValue('invalid-email');
  expect(await input.hasError('email')).toBe(true);
  
  await input.setValue('[email protected]');
  expect(await input.hasError()).toBe(false);
});

Testing Dialogs

it('should open dialog', async () => {
  const button = await loader.getHarness(
    MatButtonHarness.with({text: 'Open Dialog'})
  );
  await button.click();
  
  const dialog = await documentRootLoader.getHarness(MatDialogHarness);
  expect(await dialog.getTitleText()).toBe('Confirmation');
  
  await dialog.close();
});

Testing Menus

it('should select menu item', async () => {
  const menuTrigger = await loader.getHarness(MatMenuHarness);
  await menuTrigger.open();
  
  const items = await menuTrigger.getItems();
  await items[0].click();
  
  expect(component.selectedAction).toBe('action-1');
});

Available Harnesses

Angular Material provides harnesses for most components:

Form Controls

Input, Select, Checkbox, Radio, Slider, Datepicker

Buttons & Indicators

Button, Icon Button, FAB, Chips, Badge

Navigation

Menu, Tabs, Stepper, Tree

Layout

Card, List, Grid List, Divider

Popups

Dialog, Bottom Sheet, Snackbar, Tooltip

Data

Table, Sort, Paginator, Tree

Best Practices

1

Always use harnesses when available

If a harness exists for a component, use it instead of querying DOM directly.
2

Filter by semantic properties

Use text content, labels, or roles rather than CSS classes or ids when possible.
3

Use child loaders for specificity

When multiple instances exist, use getChildLoader() to scope searches.
4

Leverage TypeScript

Harnesses are fully typed - let your IDE help you discover available methods.

Resources

CDK Testing

Official CDK testing documentation

API Reference

Complete harness API reference

Testing Guide

Testing strategies and best practices

Custom Harnesses

Create your own component harnesses

Build docs developers (and LLMs) love