29
Tidy up your tests using component test harnesses (2/3)
In the last post, we learned about test harnesses and the value of using Component test harnesses in our automated tests. Let's take a closer look at the @angular/cdk/testing
API to understand how we work with component test harnesses.
Check out the first in this series ⬇️
Test harnesses are available for e2e and unit test environments. Out of the box, the @angular/cdk/testing
supports test harnesses in Protractor for e2e and Karma for unit tests. If you use other testing frameworks, you'll have to find an implementation of TestBedHarnessEnvironment
for your framework of choice.
We'll use the Karma unit test environment for our code examples.
This post assumes knowledge of building a site using Angular and writing unit tests using Karma. The examples shown are a simplified version from the project GitHub repo.
alisaduncan / component-harness-code
Sample app with unit tests with and without test harnesses, and a custom component test harness for the component test harness presentation
To initialize the environment and create a HarnessLoader
for your test suite, you'll set it up as part of your TestBed
setup by passing in the ComponentFixture
instance.
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
describe('Tidy Test', () => {
let fixture: ComponentFixture<TidyTestComponent>;
let loader: HarnessLoader;
beforeEach(() => {
TestBed.configureTestingModule({...});
fixture = TestBed.createComponent(TidyTestComponent);
loader = TestbedHarnessEnvironment.loader(fixture);
});
});
The above code creates a HarnessLoader
compatible with elements contained inside the fixture's DOM root. If you need to test an element outside the fixture's DOM root, such as a dialog, you can create a HarnessLoader
at the DOM root.
const rootLoader = TestbedHarnessEnvironment.documentRootLoader(fixture);
Now that we have the HarnessLoader
, we can get the component test harnesses we want to interact with. The HarnessLoader
and component test harnesses return promises, so you'll use the async/await
pattern.
You can get an individual test harness or all test harnesses.
Individual test harness
Get an individual component test harness by specifying the element type you're looking for using the getHarness
method. The method returns a test harness for the first requested test harness element type it comes across or rejects the promise if none exists.
const btn: MatButtonHarness = await loader.getHarness(MatButtonHarness);
All test harnesses
Get all component test harnesses of a type by using the getAllHarnesses
method. In this example, the method returns an array of the requested test harness element type MatButtonHarness
.
const btns: MatButtonHarness[] = await loader.getAllHarnesses(MatButtonHarness);
You can also create child loaders for certain sections of your template so you can focus on getting test harnesses. Create a child loader by passing in a selector and then get either an individual test harness or all test harnesses
const childLoader: HarnessLoader = await loader.getChildLoader('.my-selector');
const
To specify which component test harness you want to get, you can add a filter. The filtering capability and what you can filter on are unique for each component test harness. The MatButtonHarness
can filter by text to find all MatButtonHarness
with the text "delete" like this.
const btns: MatButtonHarness[] = await loader.getAllHarnesses(MatButtonHarness.with({text: 'delete'}));
Most Material component harnesses have filters built-in. So you can get all checked or unchecked MatCheckBoxHarness
, for example. You will have to read each component test harness documentation or inspect its API.
All test harnesses have a TestElement
host. The TestElement
is like the DebugElement
's nativeElement
. With the TestElement
, you can hover, click, blur, access class list and dimensions, and more! Access the TestElement
like this
const btn: MatButtonHarness = await loader.getHarness(MatButtonHarness);
const btnHost: TestElement = await btn.host();
await btn.hover();
We covered the essential functions to get test harnesses and interact with the underlying host element. How about working with the component test harness itself, such as checking the checkbox or selecting a menu item? Functions like these are unique to each component, so you'll have to refer to the API for the component test harness. Angular Material components have documentation for the component test harness so that you can look at the API overview for the component you're interested in on their site.
We've been using async/await for all the test harness interactions. We can optimize the interactions using a helper method, parallel
, which parallelizes the async operations and optimizes for change detection! For example, you can get the checked status and the label for a checkbox in one go like this.
const checkbox: MatCheckboxHarness = await loader.getHarness(MatCheckboxHarness);
const [checked, label] = await parallel(() => [
checkbox.isChecked(),
checkbox.getLabelText()
]);
We learned about using the HarnessLoader
to get test harnesses and ways we can interact with them. But what if you don't work with Material? Or what if you have custom UI components in your app? You can use the @angular/cdk/testing
API to create custom component harnesses. We'll talk about how to do that in the next post in the series.
Have you used Angular Material component test harnesses? Let me know in the comments below if you have and what your thoughts are.
29