Testing Angular components with Material's Test Harnesses

February 23, 2021

written by:

The Angular 9 release has brought along a great new feature that helps us to make our component tests more robust, more simple and more readable - component test harnesses.

angular material test harnesses framework

"In version 9, we are introducing component harnesses, which offer an alternative way to test components. By abstracting away the implementation details, you can make sure your unit tests are correctly scoped and less brittle." - Stephen Fluin (Developer Advocate for Angular)

In this article you will get a brief introduction to the topic of using Angular Material's component harnesses in your tests. Its benefits will be made obvious with an example. We will also will look at how the setup for Karma component tests differs from the already known setup.

What is a component harness?

Component test harnesses allow our tests to interact with components via supported APIs. These APIs have the ability to interact with components in a way much alike the users', and can isolate themselves from changes in the DOM. The idea for component harnesses has derived from the PageObject pattern, which is commonly used for integration tests.

“A page object wraps an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements without digging around in the HTML.” - Martin Fowler

Before component harnesses existed, CSS selectors had been a common approach to finding components and triggering events. This meant that whenever changes were being made to the component, all tests that relied on the changed component had to be updated. With the latest release, Angular 11 offers some innovations to the component harnesses for Angular Material. It is now possible to test not only individual parts of the application, but all components designed with Angular Material.

Component harnesses in Angular Material

The Angular Material team had already introduced component harnesses for all Material components before the release of Angular 9. The following example will give you a small introduction to the topic of Angular Material's component harnesses. We will demonstrate the advantages with a component test of the Material Select component.

Advantages of Material's component harnesses

  • With harnesses, you no longer need to worry about tests failing due to internal changes of Angular Material components.
  • Tests are easier to read and to understand.
  • Tests are not unnecessarily cluttered, since you no longer have to create a custom page object in order to find and to interact with Material components.
  • The same component harness can be used for Karma unit tests and Protractor based end-to-end tests.

Comparison between a classic component test and a component test with Angular Material's harness

We are testing a Material Select component that has three options. This is a test case to test if the mat-select has three mat-options and if the third option, which is being selected, has the value "Tacos".

This is the classic test without harness:

it('should be able to get the value text from a select (classic test)', () => {
  const compiledDom = fixture.debugElement.nativeElement;
  const select = compiledDom.querySelector('mat-select');
  const optionSelectList: NodeListOf<HTMLElement> = overlay.querySelectorAll('mat-option');



Let's take a look at what happens here.

  1. We need to find the element mat-select to trigger a click event on it.
  2. This opens the overlayContainer, which causes changes in the DOM. Therefore, we need to call fixture.detectChanges(). Calling the detectChanges method will ensure that any changes are also being applied to the template.
  3. We then search all mat-options and check if the optionSelectList, which is located in the OverlayContainer, has the length of three.
  4. We now click on the third option and check if the textContent of the select field equals the name of the selected option. In this case, it's "Tacos".

For comparison, let's now look at the same test case, this time using test harnesses:
it('should be able to get the value text from a select (with harness)', async () => {
  const select = await loader.getHarness(MatSelectHarness);
  await select.open();
  const options = await select.getOptions();


  await options[2].click();

  expect(await select.getValueText()).toBe('Tacos');

What is happening here?

  1. First, we get the harness of the MatSelect with the getHarness method. This method and the getAllHarness method are provided by the HarnessLoader. We will come back to this later.
  2. We then call the open method on the MatSelectHarness to open the overlayContainer with the options. The getOptions method gets us the option. The method is provided by the MatSelectHarness API.
  3. We expect three options.
  4. We then have to trigger a click-event on the third option to check if the option has been selected.

Benefits and differences

The first test doesn't seem particularly complicated, yet some parts of it are prone to error.

  • If we forget but one detectChanges method, our test will fail.
  • If any internals of the mat-select component are changed, our test will fail, even though our application might still work as we expect it to.

Tests that query the elements using selectors are generally not very robust, since these selectors can change over time.

The second test is way more readable and easier to understand than the first one. Also, a big advantage is that the implementation details are irrelevant to the purpose of testing. This means that the shape of the component model, the data binding API, and the DOM structure of the component template are unimportant because we do not need to rely on them in our test cases.

All component harness APIs are asynchronous and must return a promise. Therefore, we must use async and await.

You might have noticed the omission of fixture.detectChanges(), unlike in the first test. The component harnesses automatically call change detection after any interaction and before state is read. Another advantage is that there is also no need for fixture.whenStable(), which we often have to use in our classic tests. The harness automatically waits for the fixture to be stable, which causes the test to wait for setTimeout, Promise, etc.

Do we need a new setup?

Good news: We only have to apply small changes to our classic setup in order to use Angular Material's test harnesses. We extend the classic setup with the HarnessLoader. From the HarnessEnvironment, you can get a HarnessLoader instance, which enables us to load Angular Material component harnesses. In the beforeEach(), we create a HarnessLoader for our fixture for our SelectHarnessExampleComponent.

Classic setup

let component: SelectHarnessExampleComponent;
let fixture: ComponentFixture<SelectHarnessExampleComponent>;
let overlay: HTMLElement;

beforeEach(() => {
  fixture = TestBed.createComponent(SelectHarnessExampleComponent);
  component = fixture.componentInstance;

Setup with the use of harnesses

let component: SelectHarnessExampleComponent;
let fixture: ComponentFixture<SelectHarnessExampleComponent>;
let overlay: HTMLElement;
let loader: HarnessLoader;

beforeEach(() => {
  fixture = TestBed.createComponent(SelectHarnessExampleComponent);
  component = fixture.componentInstance;
  loader = TestbedHarnessEnvironment.loader(fixture);

As you can see, we only need to add two more lines to our previous setup.


Testing Material components requires us to test the TypeScript code plus the corresponding template - so we don't perform an isolated unit test. This makes our test more difficult than a unit test where merely a TypeScript file is tested. Testing Material components had been even more complicated and error prone before harnesses were introduced. We were required to understand the implementation details to know how to access our elements that we needed to test. Thanks to Material's harnesses, we don't need to worry about the structure of the component in our tests. Tests that make use of harnesses result in less brittle tests as implementation details are hidden from test suites.

With harnesses, the Angular Team offers us a great feature which makes our lives much easier when it comes to testing Angular Material components.

If you want to learn more about this topic, check out the guide. Among other things, you can get information on how to write harnesses for your own components in this documentation.