Automated Testing in Angular

Published On: 14. April 2021|By |8.6 min read|1709 words|Tags: , |

Hello and welcome to my first post in my new blog series about automated testing in Angular. In the first months of my profession experience, automatic software testing was not really on my scope. That was not only my fault: Usually project leaders and customers did not want to invest here as they were strongly driven by project costs. At the same time continous deployment and integration was not very common and artifacts were tested manually on multiple systems before they would be deployed to production.

Why should I have automated tests?

My latest projects are pure DevOps projects where developers are fully responsive for the product to be operational. The code increments are instantly deployed to various stages, automatically tested and available in production within minutes. The advantages are numerous, but it is absolutely necessary for this to work to be sure that the new code is working (or at least did not break anything). Nothing is more annoying for a DevOps project member/developer than explaining the product owner why your application is “currently not available”. Especially when you are the one responsible for the changes.

The solution for this are automated software tests (or test automation). This can be done with different tools and in various levels of detail. There is no 100% correct answer on which tool to use and what detail needs to be tested and what not. From my experience  I can tell that the anwer is “not 100%” – at least in the case of real world web- or single page applications. This may be wrong for libraries with many dependant packages or projects with endless development ressources.

What tools do I use for automatically testing Angular apps?

Personally, I prefer a testing stack of jest for unit tests and protractor and cucumber.js for e2e tests. Sometimes it is useful to automatically test API endpoints to make sure they do not have changed, but this will not be covered here.

While protractor is delivered with the standard Angular CLI setup, jest is replacing Karma as the default unit test runner here. The reason for this simplicity and speed. Jest runs the tests in a virtual DOM environment (js-dom) that does not require a browser to run in the background. This makes it a lot easier to run your tests directly in any CI pipeline. At the same time, test execution is way faster than with Karma and a headless browser like puppeteer for example. This is very helpful to keep your pipeline lightweight and fast.

To start fast with Angular and the setup mentioned above, you can use my angular-boilerplate template project which is available on GitHub. In a previous article you can find detailed information about what is different from a default Angular CLI project.

What should I test? What not?

The level of details for automated tests is a very important question. If you ask a dozen developers for their opinion about this, you will probably end up with no less than a dozen opinions. And most of them might be correct at the same time. There are a few things that need to be taken into consideration when thinking about what you should test.

Every tests costs ressources!

Testing (while still being necessary) costs ressources in different ways. It costs developer ressources because tests need to be implemented. Tests cost pipeline ressources, because tests have to be executed. And sometimes they cost mental ressources because you as a developer struggle a long time figuring out why that test you just wrote is not working.

Tests do not cost ressources if you don't have any tests

Do not get me wrong: I am not saying that you should not write tests at all. I have felt the pain regression bugs far too often because I was not having any unit tests at that point. But you should definitely try to find a balance between costs and benefits of testing.

To make this increasingly complex, you also need to think about where to test a single feature. Testing something in your e2e tests will make you most confident that the overall application is still working (usually including all contracts and integration with the backend), but at the same time costs most ressources in the pipeline. Unit tests are fast and easy to implement, but will not discover bugs that are caused by the interaction between different parts of your application.

The testing pyramid

This leads to the typical distribution of tests in any project: the testing pyramid. Below you will see (in green) what you will aim for when thinking in numbers of tests. Usually you will have lots (which can mean thousands) of unit tests in a large project while have less integration tests and least e2e tests (e.g. a few dozen tests). Still the unit tests are going to be way faster (just a few seconds depending on the machine running the tests) while e2e tests can take multiple minutes. And this means a lot in a CI/CD pipeline.

The testing pyramid

What you can also see in the image is that testing has changed a lot in the past years – pretty much as software development has changed as well. While it was common to do a lot of manual testing in traditional software development to find every single bug in an application, the goal nowadays is to prevent bugs in the first place. When aiming for continous integration scenarios where every single code change will directly find it’s way into the production system, you will want to be sure that the existing (functional) code is not negatively affected.

The above image is of course just an example of a testing pyramid. On Google you will find lots of different images of it, but the message is the same: Do many fast and cheap tests and use expensive, slow tests only where necessary. If you are interested in the testing pyramid, you can find a good article by Martin Fowler on his website.

Only test relevant features!

Since every test costs ressources, you will need to think about what is really relevant to test. There are a few questions that you can ask yourself to determine whether you should write a test or not.

  • Is this feature relevant for my application to work at all?
  • Will it have a huge negative effect on the user experience if this feature fails?
  • Did this feature already break before?

If you answer one of these questions with “yes”, I would recommend you write a test for it. Of course the answer to these questions will be subjective – no one defines what is a “huge” effect on the user experience for example. But that is totally okay. You are the one that needs to be confident enough with your tests that neither you or one of your colleages can easily break your code (at least without noticing).

An example on what to test

From my experience, the above questions give me a good guideline to decide what I want to test and what not. Let’s have a look into some pseudo-code examples to exemplary show what I mean:

Copy to clipboard

In the example above, I would test everything that is relevant for my form to work:

  • Is the button enabled when the entered form data is valid? (Otherwise the form could not be sent)
  • Is the button disabled when the entered form data is invalid?
  • Is the sendData() function called when the button is clicked and the form is valid?

You could test all of these in a single test of course. And remember these are only the tests for the template –  the controller part (e.g. how the sendData() function works) needs to be tested as well. The rest (like the CSS class and the button type) do not need to be tested separately. The button type will be reflected in the test where the button is clicked and the class is assumed to be only relevant for some styling that is not crucial in this example.

Do not blindly aim for 100% coverage in frontend

Altough 100% coverage looks really nice in a pipeline, it is most likely misleading in frontend unit tests. This is because coverage tools usually only measure the lines of code (in TypeScript) have been tested. Since this is only one puzzle piece in the frontend, it is simply wrong. HTML and CSS (which can also contain relevant business logic) is not taken into account.

So instead of being a slave of the coverage value, you should rather think yourself what is important to you and your team. Sometimes it is okay to skip some lines of TypeScript code in your tests and it might be more important to make sure attributes in HTML are set correctly and all elements are available in the DOM when they are expected to.

When taking testing to the extreme, you would theoretically need to test every single part of your code not only locally and in the pipeline, but also in every browser that you aim to support! I mean, there are also tools (like Browserstack) that can help here, but this is in most cases not cost-effective anymore. You will never have a real 100% test coverage. Full stop.

Summary

What you should take away from this short introduction into testing is the following:

  • Write tests for your code. Really, do it.
  • The goal of automated tests is to discover bugs before they find their way into production and break existing features
  • Performance and simplicity of tests are important (especially in CI/CD)
  • Have a lot of cheap & fast (unit-) tests and few expensive & slow (e2e- or UI-) tests
  • Only test what you consider relevant for you, your team and your product
  • No Angular project will never have a real 100% test coverage and that is okay

What’s up next?

In my next article I will describe different unit testing scenarios in Angular in detail (including code examples and a demo repository). You will find examples how to test:

  • Pipes
  • Services
  • Directives
  • Components
  • Interceptors
  • and more…

Stay tuned! Also, feel free to leave a comment below and let me know what you think about my post or ask for a specific topic you want to know more about.

My Angular Boilerplate Project
Unit Testing Angular Pipes