Clean Up Your Frontend Tests: Part 3
By: Paweł Nowakowski
Tags: Tips And Tricks, Testing, Angular, Frontend, Good Practices

In the first part of the series, we described the reasons why we focus on writing integration tests for our code. One case where this approach wouldn’t be helpful is while writing a code that will be reused in multiple places within an application.

Building the application out of small, reusable blocks is certainly tempting. You may even think there is no good reason not to do so. You can definitely take this route too far, though.

In this article, we will describe what difficulties we faced while writing and testing such code. We will also show how we deal with issues like this.

Example code

Say, we have a couple of wizards. Each of them should display a description of the current step along with the current step number.

Rendering the stepper could be done as a separate component. This way we can stay DRY, keep the look and feel consistent. A super simple stepper component could look like this:

The code of the wizard that uses it could then look like this:

Tests

Following the integration test approach, one could write tests as below:

The main issue with this code is that the exact behavior of the stepper component would be checked by multiple test files. Let’s say that at some point, we decide to display the total number of steps in addition:

We would have to go through all the tests and rewrite the expectations. This type of change can quickly add up and make changing such components very hard.

Our approach to this problem takes inspiration from the tools built into Angular, namely HttpClientTestingModule or RouterTestingModule . The person writing the StepperComponent would also create a testing module.

Note that the StepperTestingModule needs only to expose the same "public" API as the module it replaces. It means that only the exported components need to be mocked. If it makes sense to break down the component further, the child components should not be exposed to the clients. Any services, directives, or pipes that are intended only for internal use in the module should also be left out.

The mocked components should have the same selectors, @Input s and @Output s. This way, they can be later used as drop-in replacements in tests.

Just the shared components would then be covered with integration tests.

When to write reusable testing modules

Breaking the tests down brings in similar risks as described in the first part of this series. They are somewhat mitigated by the fact that you don’t need to write separate tests for each of your components. Instead, you consciously decide how to break down your application into manageable chunks.

Writing testing modules also makes us think more about what we expose from our modules. They become a natural boundary, preventing us from building a module dependency hell for ourselves, where every part of the app depends on some other part of the app, and changing anything becomes a nightmare.

There is no one-fit-all answer on when you should break out such modules. You will need to develop an intuition taking into account the number of changes and UI consistency in your particular project.

If you want to learn more here is the link to the next part of the series where you will learn how to manage tests complexity using Page Objects.