Angular Material components testing

How and why to use Angular Materials component harness to write reliable, stable and readable component tests

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer



7 min read

Angular Material components testing

Good tests are the backbone of every application. They ensure that our app runs the way we expect it. Moreover, a robust test suite facilitates refactorings. Reliable tests allow us to change things under the hood without affecting the behavior. In this article we want to show you the best way to test Angular material components.

Testing in Angular

In Angular, we distinguish between different types of tests; unit tests, component tests (integration tests) and end to end tests. The testing pyramid for Angular applications

Unit tests are the most straightforward tests to write; they simply test functions of our TypeScript code. Component- and End to end tests on the other side are a little bit harder. They don’t only test the TypeScript code, but also the template, which may include third-party libraries such as Material.

What’s the problem with today's component tests? 🤔

When writing tests for components that include Angular material, we usually require a CSS query selector.

Let’s examine this further by having a closer look at some of the tests of a “Game of Thrones” filter demo application.

Game of Thrones characters filter table

This application is pretty straight forward; it allows you to filter all characters by their “alive status” using the radio buttons. Additionally, we can also filter the characters by typing a search text in the input field.

For example, if we click on the radio button with the label “Dead”, the table is filtered accordingly.

filtered (dead) game of thrones characters

Let’s write a unit test that tests that the radio button filter works. It tests that the correct method is called and the data source is filtered accordingly. We test the interaction between our template, Material and our Typescript logic.

it('should filter out the alive caracters if we set filter to dead', (done) => {
  const deadRadio = fixture.debugElement.query(By.css('#deadFilter'));
  const clickableElement = deadRadio.query(By.css('.mat-radio-container'));;

  fixture.whenStable().then(() => {
    const rows = fixture.debugElement.queryAll(By.css('.mat-table tbody tr'));

test the filter feature by radio button on the GOT characters table

First, we get a hold of the radio button with the id deadFilter. Unfortunately, nothing happens if we would execute a click on the radio button. Therefore, we query the radio button element to get the clickable element, which is the one with the mat-radio-container class.

After we execute the click method on the clickable element we call fixture.detectChanges() to indicate to the TestBed to perform data binding. Next, we await the promise returned by fixture.whenStable(). By awaiting the Promise, we are guaranteed that JavaScripts engine’s task queue has become empty.

At this point, we can get a hold of the table rows and assert its length. Since we are using a then handler we need to call Jasmine’s done callback to ensure that the test only finished after our assertions were executed.

There are a couple of downsides with this approach, maybe you already noticed them. 😉

We rely on internal implementation details

To check a radio button, it’s not enough to get a hold of it and call .click on its nativeElement. We need to find the clickable element inside the radio button itself.

const deadRadio = fixture.debugElement.query(By.css('#deadFilter'));
const clickableElement = deadRadio.query(By.css('.mat-radio-container'));;

test code to click on a material radio button

Querying material components by using CSS selectors is bad for multiple reasons;

First, we need to understand the material components. To find the clickable element, we need to dive into its internals.

Second, what if material adjusts the internal DOM structure of the radio button or just simply renames the mat-radio-container class to mat-radio-box. Our test would fail, even if our application is still running the way we expect it to, right?

Relying on implementation details of third party libraries is cumbersome because you are vulnerable to refactorings and you need to understand implementation details

Manually stabilize the fixture

A fixture becomes stable after Angular resolved all bindings and JavaScript’s task queue is empty. To guarantee a stabilized fixture, we need to remember to call fixture.detectChanges and fixture.whenStable.

fixture.whenStable().then(() => {
  // ...

Call fixture.detectChanges and fixture.whenStable to guarantee everything is rendered

Remember to call those statements is cumbersome. “If we forget it, we may end up in long and confusing debugging sessions” (burned child 😉).

Call done when you are done

As soon as we start to assert things in an asynchronous callback (for example with Promise or Subscriptions) we need to make sure to call the done function after our assertions. If we forget this, our assertion may not be executed. Means, our test may past, even if in reality, it doesn’t.

Tipp: When we work with promises we can also work with async/await statements instead of callbacks. Then we don’t have to call the done function.

Angular Material test harness to the rescue ⛑️

The “Harness concept” is inspired by the PageObject pattern. A harness class lets a test interact with a component over an official API.

By using component harnesses a test isolates itself against the internals of component library and resists internal refactorings.

Sounds good, how can we leverage materials test harness?

let loader: HarnessLoader;

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

Setup a HarnessLoader

The first thing we need is a loader. Therefore we declare a variable in our top-level describe and assign it inside the beforeEach hook. We use the TestbedHarnessEnvironment to get ourself a HarnessLoader instance.

A HarnessEnvironment is the foundation of our harness test. There are different types of HarnessEnvironment’s. For Karma/Jasmine environment we use the TestbedHarnessEnvironment.

That’s the full setup. Now, we can go on and take advantage of component harnesses in our test. Let’s rewrite the filter test we encountered previously.

it('should filter out the alive caracters if we set filter to dead', async () => {
  const deadRadioButton = await loader.getHarness<MatRadioButtonHarness>(
    MatRadioButtonHarness.with({ label: 'Dead' }),
  const table = await loader.getHarness<MatTableHarness>(MatTableHarness);

  await deadRadioButton.check();
  const rows = await table.getRows();

component test that tests the filter by radio button function with the help of Angular materials component harnesses

Look at how simple and readable our test has become! 🤩

When writing harness tests, we heavily work with async/await. Therefore, one of the first things we should always do is to put the async keyword in front of our callback.

The loader allows us to load the harness objects. In our case, we are interested in the harness for the radio button with the “Dead” label. Once we got a hold of the harness, we can use its API to call the check method. The check method is self-explaining and internally knows how to check the checkbox — we no more need to lookup implementation details of mat-radio-button.

The same goes for the table. We only have one table on our page, therefore it’s enough to ask the loader for the MatTableHarness without specifying a selector. After we got a hold of the MatTableharness we can use the getRows function to get the number of rows.

Notice that we didn’t call fixture.detectChanges() nor fixture.whenStable(). The material component harness takes care of stabilizing the fixture once we interact with a component. This is amazing and makes our test less error-prone.

Another amazing benefit is the readability of our test. Take a look at the following two examples, what do you think? Which tests are easier to read? The classic tests or the harness test? Which one contain less boiler code?

Classic component test and harness component test which test the GOT character table’s “dead” radio button filter Classic component test and harness component test which test the GOT search field filter

Harness testing in Angular allows for easier, more readable and efficient component testing.

Do you enjoy the theme of the code preview? Explore our brand new theme plugin

Skol - the ultimate IDE theme

Skol - the ultimate IDE theme

Northern lights feeling straight to your IDE. A simple but powerful dark theme that looks great and relaxes your eyes.

Do you want to build an outstanding component library? Checkout our UI components library starter

UI components library starter

UI components library starter

Create, build, test, deliver and showcase your Angular component library.

We collected all the best practices of building Angular libraries and put them together with state-of-the-art technologies into one epic starter kit.

Get notified
about new blog posts

Sign up for Angular Experts Content Updates & News and you'll get notified whenever I release a new article about Angular, Ngrx, RxJs or other interesting Frontend topics!

We will never share your email with anyone else and you can unsubscribe at any time!

Emails may include additional promotional content, for more details see our Privacy policy.
Kevin Kreuzer - GDE for Angular & Web Technologies

Kevin Kreuzer

GDE for Angular & Web Technologies

Trainer, Berater und Senior Front-End Engineer mit Schwerpunkt auf dem modernen Web. Er ist sehr erfahren in der Implementierung, Wartung und Verbesserung von Anwendungen und Kernbibliotheken für große Unternehmen.

Kevin ist ständig dabei, sein Wissen zu erweitern und zu teilen. Er unterhält mehrere Open-Source-Projekte, unterrichtet moderne Webtechnologie in Workshops, Podcasts, Videos und Artikeln. Weiter ist er ein beliebter Referent auf Konferenzen. Er schreibt für verschiedene Tech-Publikationen und war 2019 der aktivste Autor der beliebten Angular In-Depth Publikation.


Blog posts


Blog views


NPM packages


Downloaded packages




Celebrated Champions League titles


Do not hesitate to ask questions and share your own experience and perspective with the topic

You might also like

Check out following blog posts from Angular Experts to learn even more about related topics like Angular !

Demystifying the push & pull nature of Angular Signals

Demystifying the push & pull nature of Angular Signals

Let's take a deep dive into Angular signals and how they can be understood using the primitive push & pull concepts to make sense of all their behaviors from setting a value all the way to effects and laziness!

emoji_objects emoji_objects emoji_objects
Tomas Trajan

Tomas Trajan



10 min read

Angular & tRPC

Angular & tRPC

Maximum type safety across the entire stack. How to setup a fullstack app with Angular and tRPC.

emoji_objects emoji_objects emoji_objects
Kevin Kreuzer

Kevin Kreuzer



6 min read

Jest ESM - Total Guide To More Than 100% Faster Testing For Angular ⚡

Jest ESM - Total Guide To More Than 100% Faster Testing For Angular ⚡

Let's learn how to speed up our Angular Jest tests by more than 100% by switching to Jest ESM (a notoriously problematic migration) and how to solve all the troublesome issues that tend to pop up along the way!

emoji_objects emoji_objects emoji_objects
Tomas Trajan

Tomas Trajan



13 min read

Stärken Sie Ihr Team mit unserer umfassenden Erfahrung

Unsere Angular Experten haben viele Jahre damit verbracht, Unternehmen und Startups zu beraten, Workshops und Tutorials zu leiten und umfangreiche Open-Source-Ressourcen zu pflegen. Wir sind sehr stolz auf unsere Erfahrung im Bereich des modernen Frontends und würden uns freuen auch Ihrem Unternehmen zum Aufschwung zu verhelfen.