Simultaneously to the Angular 9 release, a new version of the Material component library was released. This release includes an absolute highlight feature, Angular Material test harnesses.
Testing in Angular
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 Angular, we distinguish between different types of tests; unit tests, component tests (integration tests) and end to end tests.
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.
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.
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.
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
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
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.
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
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’re done
As soon as we start to assert things in an asynchronous callback (for example with
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/awaitstatements instead of callbacks. Then we don’t have to call the
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?
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
HarnessEnvironmentis the foundation of our harness test. There are different types of
HarnessEnvironment’s. For Karma/Jasmine environment we use the
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.
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.
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
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.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?
End to end tests
So far we only talked about component tests. But, as mentioned in the beginning, they are not the only tests in Angular that interact with our HTML and the Material components.
End to end test are another important set of tests which fall into this category. Can we use Angular Material component harnesses in an end to end test?
Yes, we can. In fact, we can use the exact same API. The only thing that differs is the loader. In our setup we would get the
loader from the
ProtractorHarnessEnvironment instead of the
You find the full source code of the GOT characters table and all the component tests used in this blog post in the following GitHub repository.
This project was generated with Angular CLI version 9.0.2. Run ng serve for a dev server. Navigate to…
Furthermore, I recommend you to check out the official docs on “Angular Material component harness”. The docs are epic!
UI component infrastructure and Material Design components for mobile and desktop Angular web applications.
With version 9, Angular Material exports harness classes that help us improve component tests.
By using harness classes, our component tests are more reliable. We interact with components over an official supported API and don’t use CSS selectors to query internal DOM structure. Our tests keeps working even if Material decides to refactor their internals.
Furthermore, we don’t have to care about stabilizing the
fixture. Materials component harnesses already take care of that.
Another huge benefit, which is not to be underestimated, is readability. Component tests that use Materials test harness are easier to read and understand.
🧞 🙏 If you liked this post, share it and give some claps👏🏻 by clicking multiple times on the clap button on the left side.
Feel free to check out some of my other articles about front-end development or download one of my open-source modules!