Good Testing Practices with 🦔Angular Testing Library
Angular Testing Library provides utility functions to interact with Angular components, in the same way as a user would. This brings more maintainability to our tests, gives us more confidence that our component does what it's supposed to do, and it improves the accessibility which is better for our users. All these benefits, plus you'll see that it's fun to write tests in this way.
You can find the completed code for this example here.
We're encouraging good testing practices across multiple frameworks and libraries by bringing a similar API to the table. These tests can be written in the test runner of your liking.
maintainable tests: we do not want to test implementation details
confidence in our components: you interact with your components the same way as your end-users
accessibility: we want inclusive components
The more your tests resemble the way your software is used, the more confidence they can give you.
To get started, the first step is to install
@testing-library/angular, after that we're good to go.
In this post, we'll take an introduction by writing tests for a feedback form, starting very simple and keep building on top of it.
The form we'll put under test has a required name field, a required rating field that must be between 0 and 10, a summary field, and a select box to select a t-shirt size. A form wouldn't be a form without a submit button, so let's add that too.
The code of the forms looks as follows.
To be able to test the feedback component we must be able to render the component, we can do this by using the
render function takes the component under test as the first argument and has an optional second argument for more options see
RenderOptions, which we'll be covering soon.
It doesn't have to be more than this as setup to test a simple component
In this case, it throws an exception because we're making use of reactive forms and some of the Angular Material components. To solve this we must provide both of the missing modules. To provide these modules we use the
imports property on the
RenderOptions, similar to how
Now, this test works.
render function returns a
RenderResult object which contains utility functions to test the component.
You'll notice that we test components in a similar way just as an end-user would do.
We don't test implementation details but
Angular Testing Library gives us an API to test the component from the outside, via the component DOM nodes.
To verify the nodes an end-user sees we use queries, these are available on the rendered component.
A query looks for the given text (as a
RegExp) in the component, which is the first argument of the query. The optional second argument is TextMatch.
In our test, to verify that the form is rendered with the correct title we can use the
In the above snippet, you don't see an assertion. This is because the
getAllBy queries throw an error when the query isn't able to find the given text in the document. If we don't want
Angular Testing Library to throw an error we can use the
queryAllBy queries instead.
The error will print out the component's DOM elements with syntax highlighting
With the component rendered, the next step is to provide the needed
@Output() properties. To assign these properties, we can use
componentProperties from the
RenderOptions. In the case of the feedback component, we assign
shirtSizes to a collection of t-shirt sizes and we assign
submitForm to be a spy. Later on, we can use this spy the verify the form submission.
With this step, the component is ready to start writing tests.
So far we've seen how we can assert our rendered components with query functions, but we also need a way to interact with our components. We can do this by firing events. Just like the query functions, these events are also available on the rendered component.
For the full list of supported events you can take a look at the source code. This post only covers the ones it needs to test the feedback component, but all of the events have a comparable API.
The first argument of an event is always the targeted DOM node, the optional second parameter is to provide extra information for the event. An example is which mouse button was pressed, or the text of an input event.
Good to know: an event will run a change detection cycle by calling
detectChanges() after the event is fired.
To click on an element, we use the
Because we're able to click on the submit button now, we can also verify that the form has not been submitted because it's currently invalid.
We can use the second parameter to fire a right click:
To make the form valid, we must be able to fill in the fields. We can use this by using various events.
Just like before, we can get our form fields by using queries. This time we get the form fields by their corresponding label, this has the benefit that we're creating accessible forms.
queryByLabelTextare also looking at
aria-labelsto find an element
To fill in form fields you notice we have two ways of doing it, the first one is via the
input event, the second one with the
type event. With
input event to set the form value, via the second parameter we assign the event's value to the value in our test case.
type is a new introduced (user-)event (introduced in