Tim Deschryver

Making sure you're using the correct query

Modified
@tim_deschryver

One of the main focus points of Testing Library is accessibility. We want to make sure tests are easy to write while keeping the user's experience in the first place. Instead of selecting DOM elements via their id attributes or classes, Testing Library is using user-friendly queries.

In the latest versions of the Dom Testing Library, and thus also the Angular Testing Library (version 9), there have been improvements to these queries. One query in particular pops out, the (get|find|query)ByRole query.

From the docs:

This can be used to query every element that is exposed in the accessibility tree. With the name option you can filter the returned elements by their accessible name. This should be your top preference for just about everything. There's not much you can't get with this (if you can't, it's possible your UI is inaccessible). Most often, this will be used with the name option like so: getByRole('button', {name: /submit/i}). Check the list of roles.

The short answer to "which query should I use", is most of the time the *ByRole query. There are some cases that this query will not be able to find the element, luckily Testing Library provides a couple of ways to find the best "fallback" query to use. Before we take a look at how to find alternatives, let's take a look at other benefits, besides accessibility, that *ByRole solves.

*ByRole provides a solution to

Querying broken text

The *ByText and *ByLabelText can't find elements that are broken into multiple elements. For example, given the following HTML, it wasn't possible to query for the text "Hello World".

This can now be solved with the *ByRole query as:

Finding multiple elements

There were times that the query I used (mostly GetByText) resulted in more than one element. This was causing some of our tests to be brittle, which is what we want to prevent.

For example, to find the login button in the above HTML I would mainly see the following.

This test would fail when a new "login" text would be added to the screen. Not good, as Testing Library aims to write maintainable tests.

A better approach to this problem was to use the selector option.

For this simple case, this approach is OK. But this could cause problems if CSS classes are used as the selector. Because the *ByRole query is more explicit, it reduces the chance of finding multiple elements.
Also, if we take a look at the selector option it resembles the *ByRole query, it's just a poor man's implementation of it. The *ByRole query is also more robust as it isn't too specific, e.g. it's possible to find heading tags with 'heading' instead of the h1 to h6 tags.

Tips to find the correct query

Sadly the *ByRole isn't a silver bullet. There are scenarios where the *ByRole query cannot be used. An example of this is query elements without a role, e.g. a password field. But you don't have to worry, the original queries can still be used and these are also making sure the application is accessible.

To find the query you need, there are several resources to use.

The docs

The Testing Library website has its own page to guide you which query to use. If you haven't read this, I would recommend it to read it. It's a short summary, but it will definitely help you to write better tests.

Testing Library Playground

Last week, Stephan Meijer created testing-playground in order to help finding the "best" available query. To use it, copy and paste the markup into the editor and click on the rendered elements or write the queries yourself.

Testing playground suggesting to use another query

A browser extension is also on the roadmap!

Testing Library configure method

I recently refactored the examples of Angular Testing Library to make use of the *ByRole query. I did this by using the newly added option throwSuggestions to the configure method. By default this is turned off and it's possible to turn it on on a global level, or on a per-query level. As the name implies, it will throw an error if there's a better/safer query is available.

As an example, we can take the following HTML and as a test, a click on the increment button.

Because the throwSuggestions flag is turned on, we get the following error while we run the test.

Testing Library gives us a better query that we can copy and paste in the test to replace the "bad" query usage.

Simple right? To turn it on or off for a specific query we can do the following, which will have the same result.

Testing Library Messages

Testing Library provides a useful message when it doesn't find an element for the given *ByRole query. See the example below, where every accessible element is logged with its corresponding selector.

More tips for Angular Testing Library

screen

In previous versions of Angular Testing Library, it was only possible to query elements via the queries returned by the render method. In version 9 of Angular Testing Library, it exports a screen object which has all the queries available. This has as benefit that your tests will become less bloated. A second benefit is that it also looks elements outside the component's HTML, this can in particular be helpful if you're using Angular Material because they will add overlays to the body of the document and outside the component tree.

fireEvent

Just like screen, to fire events it's recommended to use the newly added fireEvent object instead of using the events returned by the render method. The events fired by fireEvent will also run a change detection cycle, just like before.

Using find queries

Angular Testing Library version 9, also patches the find queries and will invoke a change detection cycle before invoking the query. This can be helpful for asynchronous code that impacts the DOM. For example, if a text input is debounced and will render something afterward. Whereas before you had to manually call detectChanges after a given time.

More tips

Kent C. Dodds wrote Common mistakes with React Testing Library a couple of weeks ago. Because the tests written for any popular framework or library resemble each other with Testing Library, most of the tips also apply to Angular Testing Library. So, give it a read for more useful tips for writing your tests with Testing Library!


Please consider supporting me if have you enjoyed this post and found it useful:

Buy Me A Coffee PayPal logo
Support the blog Share on Twitter Discuss on Twitter Edit on GitHub