Angular — A Comprehensive guide to unit-testing with Angular and Best Practices
By: Bhargav Bachina
Tags: Programming, Software Development, Web Development, JavaScript, Typescript

Angular — A Comprehensive guide to unit-testing with Angular and Best Practices

A step by step guide to a unit testing angular project with some examples

Unit Testing ecosystem

Unit testing is as important as developing a project nowadays and it becomes an integral part of development. It actually boosts the quality of the code and confidence of developers. Unit tests are written in the Jasmine framework and executed by karma, A test runner and these are executed in the browser.

Sometimes, we write tests before we even start developing which is called TDD. We mostly follow BDD since we are using a jasmine framework.

Let’s jump in right away!! Here are the concepts that we will cover in this article

Features of Unit Testing

When can we write Unit Tests

Why use Jasmine

Why use Karma

Setup an Example Project with karma and jasmine

Testing Configuration in Angular Project

Key Concepts

Testing Component Class

Testing Component DOM

Testing pipes

Testing services

Code Coverage

Best Practices

Conclusion

Check out how to execute protractor e2e tests in Docker.

Features of Unit Testing

we have two kinds of testing which are end-to-end testing and unit testing. There are specific features that qualify for unit testing. Let’s go through those.

Used for behavior tests, Jasmine encourages Behavior-driven development.

Should run very fast

Should be tested in isolation without any dependencies

Should define clear output for the input

The smallest unit can be tested as a unit

When can we write Unit Tests

We have seen features of unit tests, let’s see where we can use these

At the beginning and completion of initial development

If we add any feature to the project, we have to write unit tests for it

If we change any existing feature, we have to write unit tests for it

Why use Jasmine

Jasmine is a behavior-driven javascript testing framework. Here are some features of Jasmine

Default integration with karma

Provides additional functions such as spies, fakes, and pass-throughs

easy integration with reporting

Why use Karma

Karma is a test-runner for javascript apps. Here are some features of Karma

Angular recommends Karma and CLI create it out of the box.

We can automate tests in different browsers and devices

We can watch files without manual restart

Online documentation is good

We can easily integrate with a continuous integration server

Setup an Example Project with karma and jasmine

We don’t have to set up anything to start on unit testing. Thanks to Angular CLI, it creates all the set up (jasmine and karma) for us. Let’s test.

// create a new angular project

ng new angular-unit-testing // start the test

npm test

And here is the result in the browser

test results in the browser

For Testing different features of angular, I created a sample project which is a to-do list App. Here are the screenshot and clone it from here.



git clone // clone it from heregit clone https://github.com/bbachi/angular-unit-testing.git // install dependencies and start the app

npm install

npm start

todo App

Testing Configuration in Angular Project

When we run the npm test or ng test , Angular takes the configuration from angular.json. Look at the test part of an angular.json file.

We are specifying test.ts is our main file and karma.conf.js is our karma configuration file

testing part of angular.json

In the test.ts file, we are loading all the spec files from the project for testing and another file is karma.conf.js where we specify all the configuration such as reports, port, browsers, etc..

// Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context);

karma.conf.js

Key Concepts

There are some key concepts that we need to learn before we begin testing the app. Let’s see what are those

TestBed

This is the basic building block of the angular testing module. It actually creates a dynamically constructed test module that emulates an Angular NgModule. The metadata that goes into TestBed.configureTestingModule() and @NgModule are pretty similar and this is where we actually configure the spec file.

If we look at the below file, TestBed.configureTestingModule() takes a similar configuration as @NgModule. So, It actually creates a dynamic testing module and always put this configuration in the beforeEach() which runs before every test method.

sample file

compileComponents()

If we look at the above file again, we are using compileComponents() method. If we are creating a component with external files such as styleUrls, templateUrl, We need to read these files from the file system before we even create a component and this is asynchronous operation and that’s why the entire configuration is placed inside an async function.

What it actually doing is it reads the external files from the file system asynchronously and compiles the corresponding component. If we are running the tests with angular CLI, we don’t need to do this. This is because CLI compiles all the code before testing.

why two beforeEach functions

If we look at the below file, we are using two beforeEach functions. why do we need two?

Remember compileComponents() is asynchronous So, we want to make sure that component is compiled before we even create a fixture to access the component instance. The second beforeEach() runs after the first one.

app.component.spec.ts

Why do we use NO_ERRORS_SCHEMA

Let’s select an app.component.ts component from the above project and test it.

Look at the spec file that angular CLI generated for us when we use the command ng n c . I added f (line 5) to describe global function to run only this file when we execute tests. These are called Focused Tests

spec file angular CLI generated for us

Let’s run the npm test and we would see a bunch of errors because of nesting components. since the app component has other components inside such as header, footer, and add-item.component, etc.. We have to declare those in the app.component.spec.ts declarations array (line 11).

karma showing errors because of nesting components

Another way is to use NO_ERRORS_SCHEMA. With this, we tell angular “hey angular, ignore all the unrecognized tags while testing app.component”

Let’s use this and see the results.

usage of NO_ERRORS_SCHEMA

app component test is successful

All the introduction and configuration part is done. Let’s dive into some examples!!

Testing Component Class

With this in place, Let’s go ahead and test the app component. Let’s test this deleteItem method in app.component.ts

deleteItem method in app.component.ts

app.component.spec file

Testing Component DOM

Let’s test the footer component where we have footerText and numberOfItems. Let’s change these in the component and test the footer template DOM

the footer of the app

footer spec file

Notice the fixture.detectChanges(), we are initializing the footerText or numberOfItems in the test spec and we are invoking angular change detection cycle to make sure DOM has all these values before we can even test.

Here is the result in the browser. Since footer is a standalone component, We can even see the component rendered in the karma browser from the last spec.

test results in the browser

Testing pipes

Angular pipes are easier to test since they normally don’t have any dependencies.

I have a custom pipe that shows the current year after the footer text like below. Let’s test this one.

a pipe which displays current year after footer text

copyright.pipe.spec.ts

Here are the results

Test result in the browser

Testing services

Services are the easiest ones to test. we have one service in our project which is app.service.ts. Let’s test this one. we have two kinds of testing synch and async testing. We don’t have any async calls to test for this one.

We have an addItem method in the service, Let’s test that one.

app.service.spec.ts

Here are the results in the browser.

test results in the browser

Code Coverage

Code coverage represents how much percent of code is covered with unit tests. Usually, we have a target of 80%. Angular CLI generates the coverage report in a separate folder called coverage. Let’s see how we can generate it.

Run the following command and it will generate the coverage folder as well.

ng test --no-watch --code-coverage

coverage report in the console

coverage folder created

If you load the index.html from this folder in the browser, we can see the full report.

full coverage report

Every folder in the app has this index.html, we can load that in the browser to see the actual report for that particular folder or module. For example, Let’s load the app folder

only app module files

Best Practices

Make sure unit test runs in an isolated environment so that they run fast.

Make sure we have at least 80% of the code coverage

When it comes to testing services, always use spies from the jasmine framework for the dependencies this makes testing a lot faster.

When we subscribe to observables while testing, Make sure we provide both success and failure callbacks. This will remove unnecessary errors.

When testing components with service dependencies, always use mock services instead of real services.

All the TestBedConfiguration should be placed in beforeEach not to repeat the same code for every test.

while testing components, always access the DOM with debugElement instead of directly calling nativeElement. This is because debugElement provides an abstraction for the underlying runtime environment. This will reduce unnecessary errors.

instead of directly calling This is because debugElement provides an abstraction for the underlying runtime environment. This will reduce unnecessary errors. Prefer By.css instead of queryselector if you are running the app on the server as well. This is because the queryselector works only in the browser. if we are running the app on the server, this will fail. But, we have to unwrap the result to get the actual value.

instead of if you are running the app on the server as well. This is because the queryselector works only in the browser. if we are running the app on the server, this will fail. But, we have to unwrap the result to get the actual value. Always use a fixture.detectChanges() instead of ComponentFixtureAutoDetect. ComponentFixtureAutoDetect doesn’t know the synchronous update to the components.

instead of ComponentFixtureAutoDetect doesn’t know the synchronous update to the components. Call compileComponents() if we are running the tests in the non-CLI environment.

if we are running the tests in the non-CLI environment. Use RXJS marble testing whenever we are testing observables.

Use PageObject model for reusable functions across components.

Don’t overuse NO_ERRORS_SCHEMA. This tells angular to ignore the attributes and unrecognized tags, prefer to use component stubs.

Another advantage of using component stubs instead of NO_ERRORS_SCHEMA is that we can interact with these components if necessary. It’s better to use both techniques together

Never do any setup after calling compileComponents(). compileComponents is asynchronous and should be executed in async beforeEach. Maintain the second beforeEach to do the synchronous setup.

Conclusion

Unit testing Angular application is fun if you understand the core features of Angular Testing module, jasmine, and karma. An angular Testing module provides testing utilities to test the app, Jasmine is a BDD testing framework and karma is a test runner.