Unit testing Angular Application
February 7, 2022
n this article, we will be looking at how to write unit test cases for testing angular application that you are developing. Some of us think is it really necessary to spend time on writing test cases?
I agree that writing unit test cases require efforts. But remember the efforts spend in writing unit test cases will be worthy in long run.
If you are not convinced with writing unit test cases, then try to answer below questions(yes/no):
You had built a feature and there is a request to make changes to developed feature. Are you confident that by end of development existing functionality will work as expected?
There is an application with 100+ features, where most of the features are dependent. After every build is it possible to manually test the application thoroughly?
By doing manual unit testing, are you confident that you have 100% code coverage with no bugs?
Are your manual test cases well documented (feature by feature /component by component)?
Are you confident that time you will be spending on resolving bugs will be less than writing unit test cases?
I suppose if for any of the above questions your answer is no then its worth to write unit test cases as you develop your features.
Hopefully, you are motivated enough and realized the importance of writing unit test cases along with the development of the feature.
There is basically three type of testing that we can perform in our angular application.
- Unit Testing (Component only)
- Integration Testing (Component + Template)
- End to End Testing
We will be covering all of it in this post. Let's see how we can write unit test cases in the angular application.
Here in this post, we will be using angular CLI to create our new project as it by default provide us with the setup files required for testing. Hence, reduces our efforts to set up an application to run unit test cases. If you are new to angular CLI, I recommend going through Angular CLI Cheat Sheet.
Angular CLI uses Jasmine as a testing framework and Karma as a task runner to unit test or e2e(End to End) test the application. Before moving forward let's briefly understand about Jasmine and Karma.
Jasmine
Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.
Let's see an example demonstrating how we write test cases in Jasmine.
// script.js
function helloWorld() {
return 'Hello world!';
}
// test.js
describe('Hello world', function() {
it('says hello', function() {
expect(helloWorld()).toEqual('Hello world!');
});
});
In the example shown above, we are testing helloWorld function.
We create a test suite in Jasmine using 'describe' keyword. And using 'it' keyword we create the test case that we need to run.
Karma
Karma is a task runner for our tests. It watches all the files, specified within the configuration file, and whenever any file changes it triggers the test.
That's all you need to know to work with karma.
Now that we have knowledge of Jasmine and Karma. It's time to move ahead and get familiar with some of the testing files created by the angular CLI. To follow along with the post, I recommend you to create a new project using the command: ng new <app name>
We will be looking at the following files which are used to run unit/e2e test cases in the angular application:
- test.ts file
- karma.config.ts file
- protactor.conf.ts
test.ts file
This file is located in the src folder. Let's see what this file contains:
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
You will be hardly working with this file. Here we specify the directory or file name where our test cases are located.
karma.config.ts file
This file is located in the src folder. Let's see what this file contains:
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
This is the file where all the testing related configuration is written. We will look at some of the configurations in the file:
- Framework: Place you specify your testing framework to be used when testing the application.
- Browser: Browser where you want to run test cases.
- autoWatch: If set to true, test cases will run in watch mode. Which means if there is any change in spec file test cases will be re-run.
- port: Port you want to use to run a testing server.
protactor.conf.ts
This file is located in the e2e folder. Let's see what this file contains:
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
The file contains the configurations required to run e2e test cases. Let's look at some of the configurations in the file:
- specs: It is an array where we specify file paths which contain test cases.
- capabilities: It is an object where we specify browser-related information.
- framework: Framework that we are going to use to run e2e test cases.
We gathered some good information about unit testing an application, and now its time to understand what happens behind the scene when we run a command ng test
or ng e2e
to run unit test cases and end to end test cases.
Understand ng test command
We use ng test
command to run unit or integration test cases. Let's understand what happens behind the scenes when we run the command:
- Angular CLI loads angular.json.
- Angular CLI runs Karma with the configuration path (src/karma.conf.js) specified in angular.json.
- Karma opens the browser specified in the Karma configuration.
- Karma then instructs the browser to run src/test.ts using the testing framework specified in the Karma config.
- The angular application reads src/test.ts file and runs all spec files ending in .spec.ts in your src directory.
- Karma reports the result of the test run to the console.
- Karma watches the src file for changes and repeats steps the steps to read all spec file and re-run test cases when a file change is detected.
Understand ng e2e command
We use ng e2e
command to run end to end test cases. Let's understand what happens behind the scenes when we run the command:
- Angular CLI loads angular.json.
- Angular CLI runs Protractor with the configuration(e2e/protractor.conf.js) specified in angular.json.
- Protractor opens the browser specified in the Protractor configuration.
- Protractor then instructs the browser to run all spec files ending with .e2e-spec.ts.
- Protractor reports the result of the test run to the console.
We have base knowledge about how unit testing works in Angular application. We also grasp knowledge about the files involved and execution plan that runs behind the scene when we run our unit test cases. It's time now to write our test cases in spec files and test our components.
When we have created a new app, there is a component created by default (/src/app/app.component.ts). And along with it, a spec file is also created (/src/app/app.component.spec.ts). We will understand the spec file which is created:
// Section 1: Importing packages
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
// Section 2: Creating the test suite
describe('AppComponent', () => {
// Section 3: Configuration before running test cases
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
// Section 4: Creating the test cases
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'Testing'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('Testing');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to Testing!');
}));
});
For the better understanding, I have divided spec file created by default by angular CLI into 4 sections. These are:
- Section 1: Importing packages
- Section 2: Creating the test suite
- Section 3: Configuration before running test cases
- Section 4: Creating the test cases
Let's understand the above four section in detail and master the skill set of creating test cases in the angular application.
Section 1: Importing packages
There are few packages that we need to import to create our test cases. These are:
- TestBed: TestBed is the main entrance to all of Angular's testing interface. It will let us create our components, so they can be used to run unit tests.
- Async
- component: This is the component itself which we need to test.
Section 2: Creating the test suite
After importing packages, we create a test suite which will hold the test cases. The best practice that we follow is we create a test suite of each and every component in our application. We create a suite with the help of describe keyword.
Section 3: Configuration before running test cases
Once we have created a suite, inside it we specify the configurations that we need to load up before running any of the test cases. We do this using beforeEach
function which is being provided by the Jasmine.
Here in beforeEach function, we are configuring a testing module, and compiling up the component to test it.
Section 4: Creating the test cases
Now that we have module compiled and ready to test. We write our test cases to test the component.
Once we've set up our testing module, we'll use TestBed.createComponent
to create our component. The createComponent
method actually returns a ComponentFixture
. Our component actually lives at the fixture's componentInstance
attribute. So we try to access our component as:
const app = fixture.debugElement.componentInstance;
Now that we have a component loaded and accessible, we can perform any of the test cases that we want. Here in the example shown above, there are three test's which we are performing:
- Testing whether the component is created
- Testing the title property of the component
- Testing the content within the h1 tag
In a similar way, you can test the whole of your application including angular forms, services etc.
To know more Jasmine and the function provided by the Jasmine to perform testing, I recommend you to go through the Jasmine Documentation.
So far so good. We have knowledge about how to write test cases, but how can we tell whether the test cases that we have written has executed each and every line of the code base. You got it right, I am talking about code coverage. Let's discuss it in detail.
Code Coverage
To run test cases that will check the code coverage we need to execute:
ng test --code-coverage
The above command will create a coverage folder in our angular application, which we can serve using http-server
:
http-server -c-1 -o -p 9875 ./coverage
With above command, if you open your page on 9875 port, you can see a report which will show you code coverage.
Hope you learned something new out of the post. And you will start writing test cases when you are developing your next angular project.
If you have any questions or suggestions. Please reach out to us in the comment box section.