Unit Testing Angular Applications

Rate this content
Bookmark

Angular offers many things out of the box, including various testing-related functionalities. This presentation will demonstrate how we can build on Angular's solid unit testing fundamentals and apply certain patterns that make testing easier. Topics covered include: test doubles, testing module pattern, harnesses, "recipes" on how to test some common cases, and more!

24 min
03 Nov, 2022

Video Summary and Transcription

This talk explores unit testing in Angular applications, covering topics such as testing front-end applications, specifics of testing Angular, best practices, and educational resources. It discusses the anatomy of a unit test in both Jasmine and Jest, the setup and initial tests in Angular, testing user interaction and event handlers, testing rendered output and change detection, and unit testing parent components with child components. It also highlights best practices like using test doubles, testing components with dependency injection, and considerations for unit testing. Code coverage is emphasized as a metric that doesn't guarantee bug-free code.

Available in Español

1. Introduction to Unit Testing Angular Applications

Short description:

Welcome to my talk about unit testing Angular applications. Writing tests for your code is crucial for ensuring it runs correctly and does what it's supposed to do. We'll explore ways to test front-end applications, specifics of testing Angular, best practices applicable to other frameworks, and what to test, what not to test, and educational resources. For front-end testing, Jasmine and Jest are popular frameworks, while Cypress, Selenium, and Playwright are used for end-to-end testing. I'll focus on Jasmine for unit testing in Angular. Integration tests blur the line between unit and end-to-end tests. Angular mainly involves testing classes and creating instances. Let's explore Angular's tooling for testing and creating instances.

Hi everyone, welcome to my talk about unit testing Angular applications. First, just a quick intro about me. My name is Filip Voska, I'm from Croatia and working at Infinium as JavaScript team lead. Throughout my years one thing that I've learned is that you really want to be writing tests for your code. Nobody writes perfect code and you want some confidence that it's running correctly, that it's doing what it's supposed to do, and that's what you use testing for.

So quickly to go over the topics, we'll take a look at what are some of the ways to test front-end applications, what are some specifics to testing Angular applications, then in third part we will take a look at some best practices which are not really necessarily related exactly to Angular but they're also concepts that you can apply in other frameworks and in the end, we will have an overview of some of the things which you should test, which you shouldn't test, and what's the next steps, what are some educational resources.

Okay, so let's first take a look at what to test. If you go online, you will probably, and you Google testing, will probably see something related to the testing pyramid and it's what describes the ratio between unit integration and end-to-end tests. So some, in, let's say, the most common version of it, people say that you should have the most amount of unit tests, then a bit less of integration tests and then even less end-to-end tests. Now, there are variations of this, where some say that you should have something like this, which is like a testing hourglass, where you should have, let's say, equal amounts of unit and end-to-end tests, but a little less integration tests. And then you will also find this sort of shape, which is some kind of vase where you can put flowers in. And yeah, those are all kind of different philosophies, and that's all a topic on its own. We won't go into details about that. We'll just take a look at what matters for Angular.

And for front-end applications, you have usually a choice between Jasmine and Jest. Those are two most popular testing frameworks. With Jasmine, you would also use Karma as a test runner. Jest is kind of all-in-one solution. And Angular ships with Jasmine and Karma out-of-the-box, but it's also quite easy to use Jest with it. Then for end-to-end, you have things like Cypress, Selenium, Playwright. And these are all ways to run an automated browser where it's an actual browser running your code and you're simulating user behavior. So, I will be focusing on Jasmine today for unit testing. I will not be covering end-to-end or integration tests.

Now, integration tests, you can really do them with any of these tools because the line between end-to-end and integration and unit tests, it's kind of blurry. It depends on what you define as a unit and how many units are involved in a test. So, what is a unit in context of Angular? Well, Angular is mostly composed of different classes and those classes can be like components, directive, pipes, modules, services, etc. And then you also have functions like regular helper functions that you might have. So, mostly we are talking about testing classes in Angular and creating instances of those classes. So, let's take a look at some of the tooling which Angular gives us that makes testing and creating those instances a bit easier. So, this is the component which we will be working on.

2. Understanding Angular Components and Unit Testing

Short description:

Angular consists of a class component with inputs and outputs for communication with parent components. We have a simple example with a button that increments a counter and emits an event to the parent. Let's explore the anatomy of a unit test in both Jasmine and Jest. It involves defining a test suite, setting up initial state, and writing individual unit tests.

It's quite simple. For those who are maybe not familiar with Angular, Angular consists of a class component that has inputs and outputs and these are the ways that the component can communicate with parent components. So, parent can pass something via input down to the child and the child can basically emit an event back to the parent using an output.

In our example we have a really simple component where you can see in the template on line 4 we rendered the amount of time that the button has been clicked and on line 6 we have a button with attached click handler that calls some method and that method is defined in the class and it just increments the counter and emits the event to the parent. So, this is one of the most basic components that you could have in Angular.

So, let's take a look at the anatomy of a unit test. And this is really the same in both Jasmine and Jest. There are some other differences between them but this is really the same and this is the same in many other languages I would say as well. So, first you would define a test suite. So, we are defining a test suite for counter component, we use describe function for that. Then we have some... We usually have before each. Before each is a piece of code which will run before each test, each individual test and here you would set up some state and some initial state. Then between lines four and six we finally have one individual unit test. It's using function it. It's a bit weird naming but it's called like that because the way you're supposed to read this is counter component, it should do something, so that's why the function is called it.

3. Angular Testing Setup and Initial Unit Tests

Short description:

Let's expand on the basic scaffolding for the test by adding Angular-specific things. We have component and fixture, and we set up a testing module. We create the fixture and get the component instance. We have the first unit test for the Counter component, checking initialization and initial state. We test the rendered pre and button elements, and check the initial state of the counter.

Okay, so let's expand a bit on this basic scaffolding for the test by adding in some Angular-specific things. So here we have component and fixture. Component and fixture are related, so fixture is something that has some extra methods and helpers that make interaction with the component a bit easier in the tests and it allows us to query some stuff as we will see a bit later. And the component is the instance of the component class itself. So we will have references to two of those and we will be using them quite a lot throughout the tests.

And then on lines 11 through 13 we set up a testing module. So angular components are run inside of a module both in runtime and in tests. So here we are setting up a testing module inside which the application inside which the component will be running. Here we define any dependencies that the component might have. Here we're dealing with a really simple component which doesn't have any dependencies so we just import that component. Then on lines 15 and 16 we create the fixture using Create Component and we get the component instance. Line 17 is something quite specific to Angular, where we call Detect Changes. This is what triggers re-rendering. So in runtime when the application is running, change detection, which is the process which is in charge of re-rendering when necessary. It is let's say automatic but in tests you have to do it a bit more manually. Now finally between lines 20 and 22, we have our first unit test for our Counter component. We simply assert that the component is initialized, that the class instance exists. So it's a truthy value, the component.

Now let's test some initial state. So we will use our before each method to query the pre element and to query the button element. And we will store them in some variables in the test suite, because we will be using them in multiple places, so it's more reusable. So here we can see that we use fixture debug element query by CSS, and we get the pre element. We do the similar thing for the button, and for the pre element we also read the native element of it. Then finally on lines, now we have three unit tests, so we check that the rendered pre element exists, we check that the rendered button element exists, those are the first two tests, and in the final test we check that the initial state of the counter is set to one, and that's kind of just the baseline test to see that everything works initially. Now we want to check what gets rendered. It's quite important that you check what actually gets rendered as final HTML, because someone could delete the whole template of the component, and if you were only testing the component state, like checking the values of class properties, all your tests would still pass even if someone deletes the whole template. So that's why it's important to check what gets rendered, and that's why it's important to actually click on elements, etc. We'll see more about that. But yeah, here you remember we created our pre-element. It's an HTML element, and we just checked that the text content is times clicked one, and that's also the initial state.

4. Testing User Interaction and Event Handlers

Short description:

When testing user interaction, it's crucial to trigger event handlers instead of calling handler methods directly. This ensures that the test fails if the button being clicked does not exist.

This is how it relates to the template that we have. Now just like we want to check what gets rendered, we also want to check what happens when we do user interaction, and now it's important when doing user interaction testing, you shouldn't be calling handler methods on the class directly. What you have to do is you have to trigger the event handlers. For similar reasons as why you want to check what gets rendered, someone could delete your template. If you're just calling the click handler directly, it will still pass, but if you're actually trying to click on the button and the button doesn't exist, the test will fail, okay? That's why it's important.

5. Testing Rendered Output and Change Detection

Short description:

In unit testing Angular applications, it's important to manually trigger change detection after user interaction simulation. This ensures that the rendered output matches the expected values. By checking what gets rendered, you can ensure that everything is working correctly.

Yeah, so here on line seven and eight, we assert that the counter is initially one and the times clicked one is the rendered text. Then we click, then we check. So the way we click is we trigger event handler programmatically. This relates to the click handler that we attach, and this test passed. Now we check that the counter is two, but you will see that it seems like I have maybe a typo here. So I checked that the component counter is two, but the test passes and what is actually rendered is that the text is still times clicked one, it's not times clicked two. And this is because as I've mentioned, change detection, which is a process for re-rendering, it doesn't run automatically in tests like it does in runtime, when the application is actually running. This is why you have to call it manually after some user interaction simulation. So if you trigger some user interaction programmatically, you have to call detect changes to re-render. Now, the test will fail because times clicked one is not what actually gets rendered. What actually gets rendered is times clicked two. And you can see in the error log, you get like a nice message where it says what was the expected value and what was the actual value. Now we just fix it by updating one to two so now this test is valid. So two most important things here is you have to kind of know when you have to re-render in tests. It's a bit tedious, but you have to do it. And the second thing is you have to check what gets rendered in order to be fully sure that everything is working correctly.

6. Unit Testing Parent Component with Child Component

Short description:

Now we are writing unit tests for our counter component. We test the template of a parent component, checking if it uses the child component correctly. We use a query by CSS to select the child component, get the component instance, and check that the counter input is set to 100. We can also test the parent component's reaction to the counter change event by console.logging the new value. We use Jasmine's spy-on to check if console.log has been called with the expected value.

Now we are writing unit tests for our counter component. But let's say this is, we are now looking at the template of a parent component. So we want to test like how is our parent component using this child component? And this is now a unit test of a parent component, where we can get the component instance of the child component. So here you can see we will use a query by CSS test.js counter and test.js counter is the selector of our child component. You can see in the template it's being used like that as an element selector. Then we get the component instance and we check that the counter input is set to 100. So we are basically checking that our template is doing what we are expecting it to do setting counter to be a 100 initially. We can test the other way around. So if our parent component wants to react on counter change event we can write tests for that as well. So let's say that on counter change we just console.log the new value in the parent component. This dollar event is a special piece of syntax but this will be the value with which the event is emitted. So in this case it will be a number that gets emitted on counter change and that's the value that gets passed to the handler method. So here the test is expanded a bit more because we are now using spy-on. So spy-on is something from Jasmine. It's very similar in Jest as well. So basically you take console.log and you spy that it has been called. So on line 3, we set up the spy and on line 5, we expect that it hasn't been called. On line 7, we trigger the event handler to, let's say, emit 101 as the new counter value, and then we expect the console.log has been called once with value 101. So it's important to check how many times something was called beforehand, how many times it was called after, and with which value it was called. And it's all kind of nicely... you usually want to check that something was called once and with some value, and that's why I have this nice method to have been called once with something. And this is how the event relates and how the event and handler name relates between template and the test.

7. Best Practices: Test Doubles

Short description:

Test doubles are a best practice in unit testing Angular applications. When your component depends on a service that makes API calls, you can create a test double of the service. This test double, also known as a mock, has the same methods as the real service but returns mock data instead of making actual API calls.

Okay, so let's take a look at some of the best practices. Test doubles. So generally, if your application depends on some, let's say, service, and services in Angular are just classes that offer methods and they are most commonly used for fetching data. And here our user service is fetching... like making an API call and fetching the list of users. However, when you're unit testing, you don't really want to make API calls. And if your component depends on user service, you want to avoid making API calls. So, for that purpose, you would create a test double of user service. We usually call it here user testing service, but you could call it something like user service mock or something like that. And here you define all the same methods that the real service has, but it doesn't make a real API call, but it returns some mock data.

8. Testing Components and Best Practices

Short description:

Angular uses dependency injection for injecting services in components. In unit tests, we provide a test double version of the service. The same approach can be applied to components. By using test doubles, we can test a parent component without integrating with its dependencies. Angular CDK provides harnesses, which offer extra methods for testing components. Coverage is a metric that shows how much of the code has been executed by tests. However, it doesn't include HTML templates and 100% coverage doesn't guarantee bug-free code.

And the way, yeah, and this is how it's used in some user table component, for example. So, Angular uses dependency injection. And this component would inject the user service. But when we're writing tests, we provide user testing service under the user service. So, the component still expects that it will get like user service instance. But when the tests are run, then you get the mock version, the test double, the testing version of the service, whatever you want to call it.

And you can do pretty much the same thing for a component. So, component also has some methods. It has some dependencies. You can create a testing version of the component, for which it's important that it has the same selector. But it doesn't have to have the complex template, and it doesn't have to have any implementation logic. It just needs to have all the inputs and outputs. And you do that very similarly. So, if your parent component depends on the counter component, in the unit tests for the parent component, you would import counter testing component. And that way you don't use the real component. You use a mock, and this is what makes it a unit test, not an integration test. So, you're not testing both components at the same time. You're not testing the parent component and the counter component at the same time. You're just testing the parent component and using a test double version of the counter.

Next topic is harnesses. Now, I won't go into harnesses in too many details, but they are something that's provided by Angular CDK, and you can define a harness for some component. And that harness will have some extra methods which are useful for testing. So, in an example of material checkbox, there you have a harness, which makes it easier to toggle the checkbox in tests, etc. But, yeah, I won't go into too many details. You can also write harnesses for your own components. Now, last topic about best practices is coverage. So, coverage is a metric which tells you, when you gather all of your tests, like how much of the code has been covered. And this is, you can get a nice-looking report like this, and if you take a look into one specific file, you can see which parts of the code have been executed and which have not. Generally, this is a good thing, but it's not something that you should really blindly chase, because coverage in Angular doesn't include HTML templates, so you won't get that in the coverage. 100% coverage, of course, doesn't mean you don't have bugs, and depending on the setup, some files might not be even included in the coverage, so you will get actually wrong numbers, and we have some documentation about that.

9. Unit Testing Best Practices and Considerations

Short description:

Code coverage is often a lie, but project managers like it. Unit test business logic, services, core UI components, sensitive data handling, helpers, errors, and loading states. Test if something gets re-rendered correctly. Don't unit test third-party libraries or implementation details like private methods. Avoid testing complex interaction between units and complex UI flows. Unit tests are best for testing more complex code that could break. Consider the complexity of setting up unit tests compared to the value of the feature.

So, yeah, code coverage, it's often a lie, but that's a number that project managers like to hear about. Okay, so let me conclude some of these topics. So, what should you unit test? You should unit test business logic, services, core UI components, sensitive data handling, helpers, errors, and loading states, test if something gets re-rendered correctly, and anything really related to datetime handling and time zones, because it's really, really painful to debug, so having good unit tests for those things, that's really nice, but there are also some things which you shouldn't really unit test, and that's like third party libraries, your implementation details, and what I mean by this, you shouldn't like test private methods, you shouldn't be calling them directly, you should always test something in a way that it's used, so if you're testing a component, test it, write your unit test as if you were a parent component, don't go calling private methods. You also shouldn't test like complex interaction between units, that's like integration tests, that's like not for unit test, it's a different kind of test. Complex UI flows are also quite hard to do with unit testing, especially if it's like things like swiping, etc, so that's best left to end to end testing. If you have some like really trivial code, or some code that is here only temporarily, you won't really get too much benefit from unit tests, that code probably won't break. You want to test things which are like more complex, which could break, and you want to ensure that those don't break. And again, setting up unit tests can sometimes be quite complex. So if the time it takes to set up unit test is much greater than the feature is worth, then maybe consider skipping it and do some other type of testing.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

Remix Conf Europe 2022Remix Conf Europe 2022
23 min
Scaling Up with Remix and Micro Frontends
Top Content
Do you have a large product built by many teams? Are you struggling to release often? Did your frontend turn into a massive unmaintainable monolith? If, like me, you’ve answered yes to any of those questions, this talk is for you! I’ll show you exactly how you can build a micro frontend architecture with Remix to solve those challenges.
Remix Conf Europe 2022Remix Conf Europe 2022
37 min
Full Stack Components
Top Content
Remix is a web framework that gives you the simple mental model of a Multi-Page App (MPA) but the power and capabilities of a Single-Page App (SPA). One of the big challenges of SPAs is network management resulting in a great deal of indirection and buggy code. This is especially noticeable in application state which Remix completely eliminates, but it's also an issue in individual components that communicate with a single-purpose backend endpoint (like a combobox search for example).
In this talk, Kent will demonstrate how Remix enables you to build complex UI components that are connected to a backend in the simplest and most powerful way you've ever seen. Leaving you time to chill with your family or whatever else you do for fun.
JSNation Live 2021JSNation Live 2021
29 min
Making JavaScript on WebAssembly Fast
Top Content
JavaScript in the browser runs many times faster than it did two decades ago. And that happened because the browser vendors spent that time working on intensive performance optimizations in their JavaScript engines.Because of this optimization work, JavaScript is now running in many places besides the browser. But there are still some environments where the JS engines can’t apply those optimizations in the right way to make things fast.We’re working to solve this, beginning a whole new wave of JavaScript optimization work. We’re improving JavaScript performance for entirely different environments, where different rules apply. And this is possible because of WebAssembly. In this talk, I'll explain how this all works and what's coming next.
React Summit 2023React Summit 2023
24 min
Debugging JS
Top Content
As developers, we spend much of our time debugging apps - often code we didn't even write. Sadly, few developers have ever been taught how to approach debugging - it's something most of us learn through painful experience.  The good news is you _can_ learn how to debug effectively, and there's several key techniques and tools you can use for debugging JS and React apps.
TestJS Summit 2022TestJS Summit 2022
27 min
Full-Circle Testing With Cypress
Top Content
Cypress has taken the world by storm by brining an easy to use tool for end to end testing. It’s capabilities have proven to be be useful for creating stable tests for frontend applications. But end to end testing is just a small part of testing efforts. What about your API? What about your components? Well, in my talk I would like to show you how we can start with end-to-end tests, go deeper with component testing and then move up to testing our API, circ
TestJS Summit 2022TestJS Summit 2022
20 min
Testing Web Applications with Playwright
Top Content
Testing is hard, testing takes time to learn and to write, and time is money. As developers we want to test. We know we should but we don't have time. So how can we get more developers to do testing? We can create better tools.Let me introduce you to Playwright - Reliable end-to-end cross browser testing for modern web apps, by Microsoft and fully open source. Playwright's codegen generates tests for you in JavaScript, TypeScript, Dot Net, Java or Python. Now you really have no excuses. It's time to play your tests wright.

Workshops on related topic

React Summit 2022React Summit 2022
117 min
Detox 101: How to write stable end-to-end tests for your React Native application
Top Content
WorkshopFree
Compared to unit testing, end-to-end testing aims to interact with your application just like a real user. And as we all know it can be pretty challenging. Especially when we talk about Mobile applications.
Tests rely on many conditions and are considered to be slow and flaky. On the other hand - end-to-end tests can give the greatest confidence that your app is working. And if done right - can become an amazing tool for boosting developer velocity.
Detox is a gray-box end-to-end testing framework for mobile apps. Developed by Wix to solve the problem of slowness and flakiness and used by React Native itself as its E2E testing tool.
Join me on this workshop to learn how to make your mobile end-to-end tests with Detox rock.
Prerequisites- iOS/Android: MacOS Catalina or newer- Android only: Linux- Install before the workshop
React Day Berlin 2022React Day Berlin 2022
86 min
Using CodeMirror to Build a JavaScript Editor with Linting and AutoComplete
Top Content
WorkshopFree
Using a library might seem easy at first glance, but how do you choose the right library? How do you upgrade an existing one? And how do you wade through the documentation to find what you want?
In this workshop, we’ll discuss all these finer points while going through a general example of building a code editor using CodeMirror in React. All while sharing some of the nuances our team learned about using this library and some problems we encountered.
TestJS Summit - January, 2021TestJS Summit - January, 2021
173 min
Testing Web Applications Using Cypress
WorkshopFree
This workshop will teach you the basics of writing useful end-to-end tests using Cypress Test Runner.
We will cover writing tests, covering every application feature, structuring tests, intercepting network requests, and setting up the backend data.
Anyone who knows JavaScript programming language and has NPM installed would be able to follow along.
Node Congress 2023Node Congress 2023
63 min
0 to Auth in an Hour Using NodeJS SDK
WorkshopFree
Passwordless authentication may seem complex, but it is simple to add it to any app using the right tool.
We will enhance a full-stack JS application (Node.JS backend + React frontend) to authenticate users with OAuth (social login) and One Time Passwords (email), including:- User authentication - Managing user interactions, returning session / refresh JWTs- Session management and validation - Storing the session for subsequent client requests, validating / refreshing sessions
At the end of the workshop, we will also touch on another approach to code authentication using frontend Descope Flows (drag-and-drop workflows), while keeping only session validation in the backend. With this, we will also show how easy it is to enable biometrics and other passwordless authentication methods.
Table of contents- A quick intro to core authentication concepts- Coding- Why passwordless matters
Prerequisites- IDE for your choice- Node 18 or higher
JSNation 2022JSNation 2022
116 min
Get started with AG Grid Angular Data Grid
WorkshopFree
Get started with AG Grid Angular Data Grid with a hands-on tutorial from the core team that will take you through the steps of creating your first grid, including how to configure the grid with simple properties and custom components. AG Grid community edition is completely free to use in commercial applications, so you’ll learn a powerful tool that you can immediately add to your projects. You’ll also discover how to load data into the grid and different ways to add custom rendering to the grid. By the end of the workshop, you will have created and customized an AG Grid Angular Data Grid.
Contents:- getting started and installing AG Grid- configuring sorting, filtering, pagination- loading data into the grid- the grid API- add your own components to the Grid for rendering and editing- capabilities of the free community edition of AG Grid
React Summit US 2023React Summit US 2023
96 min
Build a powerful DataGrid in few hours with Ag Grid
WorkshopFree
Does your React app need to efficiently display lots (and lots) of data in a grid? Do your users want to be able to search, sort, filter, and edit data? AG Grid is the best JavaScript grid in the world and is packed with features, highly performant, and extensible. In this workshop, you’ll learn how to get started with AG Grid, how we can enable sorting and filtering of data in the grid, cell rendering, and more. You will walk away from this free 3-hour workshop equipped with the knowledge for implementing AG Grid into your React application.
We all know that rolling our own grid solution is not easy, and let's be honest, is not something that we should be working on. We are focused on building a product and driving forward innovation. In this workshop, you'll see just how easy it is to get started with AG Grid.
Prerequisites: Basic React and JavaScript
Workshop level: Beginner