How to Write Better UI Component Tests With Testing Library

Tests that mimic user behavior help us create better software products

Ali Kamalizade
Nov 25, 2020 · 8 min read
Image for post
Image for post
Photo by Ferenc Almasi on Unsplash

Testing is an important part of software development. Although testing is still sometimes regarded as an afterthought, especially in time-critical projects, methodologies like Test-driven development (TDD) and improved tooling for testing have done a lot in recent years to rectify this.

There are great testing frameworks out there for writing unit tests, component tests, and end-to-end tests in web projects. For UI testing end-to-end testing frameworks like Cypress or Selenium are often used. As with all things, there are good and bad things about end-to-end tests:

  • Initially, end-to-end tests seem easy to write. However, it’s easy to write brittle end-to-end tests that have to be adapted whenever there are UI changes.
  • End-to-end tests typically require a real browser like Chrome to run. The advantage is that you can visually see the UI interaction in the test. On the flip side, these tests usually take longer as downloading a browser, loading the webpage and interacting with the UI take time.
  • If end-to-end tests rely on HTTP calls to services and third-party APIs you have to deal with network issues, tighter coupling to other services and flakiness.

You may be familiar with the famous testing pyramid. It roughly shows us the division of effort we should aim for when writing software tests.

The testing pyramid
The testing pyramid

Why Don’t We Have More Component Tests in Frontend Projects?

At the company I work for we strive to have solid test coverage. Regardless of the programming language or project scope we want to ensure that testing is in place early. We do this by setting up test coverage thresholds and a testing framework to facilitate writing of tests.

My favorite framework for creating web applications is Angular, which already brings a solid testing foundation. To improve the developer experience we switched to Jest which is a versatile JavaScript testing framework. Jest helps us to write tests that run fast and we can easily mock and stub dependencies (e.g. third-party libraries). Thanks to Jest, TypeScript and Angular’s testing API we kept adding more unit tests by default, while at the same time reducing the number of end-to-end tests.

However, I felt that some developers were still hesitant to write tests for Angular components and directives. Components are the building blocks of creating modern web applications and static websites. Yet, we don’t often write tests for something that is core to our projects.

  • I have often seen component tests where the component is instantiated like a plain class, not a real Angular component.
  • In many of these component tests, user interaction doesn’t reflect reality — events are simulated or functions are invoked without any user interaction.
  • Sometimes, developers just deem it to not be important to write tests for components. I remember some conversations where the answer to why a component test was missing was something like, “It’s just a select component, what’s there to test?”
  • I was also not satisfied with the way you would read these tests to understand what’s being tested.

The Angular CLI creates a basic unit test by default when generating code like a new component or a new directive. Here’s a simple example that is testing that WelcomeComponent outputs the right text in a <p> element after clicking a button.

My personal issues with tests that look like the example above:

  • The test setup is not super intuitive. If you know Angular even a little then you probably understand that you need to provide the dependencies, such as imports of Angular modules. Still, the code looks more complex than it should.
  • Change detection in unit tests is often a source of confusion, especially for beginners. By default, Angular won’t do change detection as we are used when running the application. When updating a property or simulating an event we need to let Angular pick up these changes or they won’t be reflected in the DOM. Besides, components using OnPush change detection aren’t easily testable (although there’s a good workaround for this — I plan to create a separate post on this in the near future).
  • The tests are sometimes not easy to read. While you can give your test cases meaningful titles I have often come across test code where I wouldn’t know what was being tested if I didn’t read the title of the test case.
  • The test setup doesn’t provide a way to initially set up component properties without reaching down to the componentInstance.
  • If you want to write a proper test for an Angular directive you need to create a dummy wrapper component that uses the directive you actually want to test.
  • Any event (e.g. by @Output properties) can be simulated using triggerEventHandler. This is useful for events that are hard to replicate in unit tests (e.g. infinite scroll). If we use this too much we have tests that might not be accurate, though. Example: Suppose there’s a test for a component that would call a function when a child component emits an event. This test could work even if the child component does not emit the event itself when we’re simulating the event using triggerEventHandler.
  • For finding DOM elements you can either use Angular testing API (e.g. query(By.Css('a-css-selector')) or plain JavaScript (e.g. document.querySelector('a-css-selector'). While this is fairly easy, relying on CSS selectors leads to tests that can easily break. Besides, a user won’t find elements on a webpage by a CSS either so we ideally don’t do that either. A user would for example want to get a form field which is described by a label with the text xyz.

When I found the tweet below in my timeline it gave me the idea to look for a solution that could be easily integrated into new and existing projects.

This is when I stumbled over Testing Library. Let’s see what Testing Library brings to the table.

How to Write Component Tests with Testing Library

Testing Library is a project started by Kent Dodds. It started as a React-only project. Today, it supports a variety of additional frameworks like Angular, Cypress, Vue.js, and more, as well as plain JavaScript. It’s neither a test runner like Jest nor tied to a specific test framework, which makes it easy to integrate the Testing Library in new or existing projects.

Testing Library is built on the principle that the more your tests resemble the way your software is used, the more confidence they can give you:

  • Tests only break when your app breaks, not implementation details
  • Interact with your app the same way as your users
  • Built-in selectors find elements the way users do to help you write inclusive code

Sounds promising? I think Testing Library delivers on its promise. Here’s an example of an Angular component test that uses Angular Testing Library building on top of Testing Library. The component being tested is a select component which contains tags as selection options.

Let’s have a look at the difference to how a typical Angular component test looks like:

  • We are calling a render function to create the component we want to test. The Angular Testing Library will invoke the Angular testing API under the hood (see how you can specify options like declarations and imports?) but we can do more like initializing component properties.
  • Instead of simulating events, we try to mimic user behavior as closely as possible by interacting with the UI. In one test case, we first press the tab key to move the focus to the select component, then press the ENTER key to open it, and finally click the option which contains the text Silver. This has the nice side effect that the code is much more readable in my opinion as the Testing Library API is rather expressive.
  • We aren’t doing any manual change detection as the Angular Testing Library takes care of it when rendering a component or performing user interactions.
  • For finding DOM elements we mostly avoid querying by CSS. Instead of using selectors like CSS classes, the Testing Library provides multiple ways to query elements: e.g. by partial text or by role.
  • We can test more easily how accessible a component is. Testing Library helps to write test code that can uncover accessibility issues like missing keyboard navigation.
  • The RenderResult contains a reference to the ComponentFixture which is under the hood. This means we can still do things like simulating events or manually triggering change detection if we want to.
  • The test above does very little mocking. If possible I’d like to avoid mocking as much as possible in order to write tests that are as close to reality as possible.

This allowed us to get closer to the state Kent Dodds describes as “The Testing Trophy” (highly recommended blog post!).

As you can see, there’s a stronger focus on integration tests than in the test pyramid mentioned above. In front end projects, a component test can be an integration test (think of smart components that talk to different services and use multiple child components under the hood) but it can also be closer to a unit test (for example a test for button component).

In the post mentioned above, Kent gives a good example which I’d like to repeat in order to highlight the importance of integration tests in front end projects:

It doesn’t matter if your component <A /> renders component <B /> with props c and d if component <B /> actually breaks if prop e is not supplied. So while having some unit tests to verify these pieces work in isolation isn't a bad thing, it doesn't do you any good if you don't also verify that they work together properly. And you'll find that by testing that they work together properly, you often don't need to bother testing them in isolation.

Integration tests strike a great balance on the trade-offs between confidence and speed/expense. This is why it’s advisable to spend most (not all, mind you) of your effort there.

Conclusion

Thanks for reading this post about how to write better tests with Testing Library. While I focused on Angular component tests, you can use Testing Library in all sorts of web projects. With Testing Library you can easily write UI tests that avoid testing implementation details and rather focus on the functionality your UI provides.

Have you tried out Testing Library? What are your experiences with writing UI tests? Let me know in the comments.

Better Programming

Advice for programmers.

Sign up for The Best of Better Programming

By Better Programming

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Thanks to Zack Shapiro

Ali Kamalizade

Written by

Senior Software Engineer @LeanIX. Co-founder of Sedeo. Passion for software engineering and startups. Looking forward to build great things. 有難うございます。🚀

Better Programming

Advice for programmers.

Ali Kamalizade

Written by

Senior Software Engineer @LeanIX. Co-founder of Sedeo. Passion for software engineering and startups. Looking forward to build great things. 有難うございます。🚀

Better Programming

Advice for programmers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store