Unit testing is a mandatory process, by which we can identify all the defects related to security, safety, and reliability. Unit testing helps us to improve the quality of our code. Now, the web applications developed using Angular framework need to be tested, thus a well-known testing framework Jasmine is used for the same purpose. This framework is widely used by angular developers to test their web applications.
Angular CLI provides a command, that when executed, performs all the tests on the Angular components and gives us a complete list of all passed and failed cases. Also, we need a test runner which is used to display the code coverage.
Here, we use Karma which is a simple tool to display our code coverage. The main purpose of this tool is to help us track the progress of angular components' test cases.
As we mentioned above, unit testing is an important part. As of now, there are special roles for software engineers called "QA Analyst". QA stands for Quality Assurance. This particular field is for performing testing on the application.
Below are some extra points mentioned to deeply understand the importance of testing.
With the help of unit testing, we can catch bugs and issues at the beginning phase of development. It will not only reduce cost but also the effort required to fix bugs in the project.
The software developers will be encouraged to write not only a modular code but also a maintainable code. It will lead developers to think about the U.I. and interface design and component's functionalities, which results in a better code that will be more structured and write clean code compared to the old one.
Developers can confidently restructure their code once they achieve the best suite of test cases for their particular source code.
In the hiring process as well as the recruitment process, most developers are asked about their favourite tool for doing angular application testing. To test an angular component, directive, pipe, etc. there are various frameworks available in the market. We recommend the Jasmine framework and Karma tool. Although there are other frameworks too, they are mentioned below in the form of an image.
Tools and Frameworks required to make testing simpler for Angular developers.
Jasmine, a behavior-driven development (BDD) framework, stands as a cornerstone for testing JavaScript code. Renowned for its simplicity and robust features, it is programming language that's extensively employed in testing Angular applications.
With Jasmine, developers benefit from a clean syntax for articulating test cases and an embedded assertion library for defining expectations. Its suite and spec organization feature facilitates the management of extensive test suites with ease.
Karma, a test runner crafted by the Angular team, specializes in executing unit tests. Seamlessly integrating with Jasmine and other testing frameworks, Karma enables developers to run tests across diverse browsers, languages and devices.
Providing real-time feedback during development, Karma automatically re-runs tests upon detecting code changes. Additionally, it offers code coverage reports to pinpoint areas of insufficient test coverage.
Protractor, primarily an end-to-end testing framework, extends its functionality to support unit testing using Jasmine. Ideal for scripting functional tests that emulate user interactions, Protractor's Angular-specific capabilities include testing directives and components. Its comprehensive approach makes it invaluable for thoroughly testing Angular applications.
TestBed, a utility provided by Angular, facilitates the isolation testing of Angular components. Developers can create a testing module containing essential dependencies like services and providers. By utilizing TestBed, developers can instantiate and manipulate components within a controlled environment, simplifying the testing of individual components without reliance on the entire application context.
Below code snippet below shows the usage of TestBed for TestComponent.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestComponent } from './test.component';
describe('TestComponent', () => {
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TestComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
}
Angular offers a suite of utilities tailored for testing Angular applications. These utilities include ComponentFixture for programmatically interacting with Angular components, TestBed for configuring testing modules, and async and fakeAsync utilities for testing asynchronous code. Leveraging these utilities empowers developers to craft concise and expressive tests that accurately reflect their application's behavior.
While AngularJS is an older version of Angular, ngMock, a module provided by AngularJS, remains relevant for testing AngularJS applications. ngMock enables developers to mock dependencies like HTTP requests and services, enhancing the efficiency and reliability of unit testing in AngularJS applications.
In this section, we are going to discuss different methods used for testing code using the Jasmine framework.
The beforeEach function is a Jasmine function used to set up the testing environment before each test case runs. It allows developers to define common setup steps that need to be performed before executing individual test cases.
Common use cases for beforeEach include initializing component instances, setting up mock dependencies, or configuring the TestBed module. By using beforeEach, developers ensure that each test case starts with a consistent and predictable state, improving the reliability and maintainability of the test suite.
Similarly, the afterEach function is used to clean up the testing environment after each test case is completed. It's often used to reset stateful variables, unsubscribe from observables, or tear down resources created during the test. Like beforeEach, afterEach helps maintain a clean and isolated testing environment, preventing side effects between test cases and improving the overall stability of the test suite.
The it function, also known as a spec in Jasmine, defines an individual test case within a test suite. It takes a description of the test case as its first argument and a function containing the test logic as its second argument. Developers use it to articulate the expected behavior of the component or service being tested. Within the test function, developers typically interact with the component or service under test and make assertions using the expected function.
it('should divide two numbers correctly', () => {
const quotient : number = 12 / 4;
expect(result).toBe(quotient);
});
The expect function is used to define expectations or assertions within a test case. It takes a value or expression as its argument and returns an "expectation" object that provides a set of matcher functions to perform comparisons. Matchers like toBe and toEqual are used to verify that the actual value matches the expected value.
it('should count total characters in the string', () => {
const str : string = 'MEOW MEOW';
expect(str.length).toBe(9);
});
The toBe matcher is used to perform strict equality (===) comparisons between the actual and expected values. It checks if both values are the same object reference or primitive value without performing any type of coercion. Developers typically use toBe when comparing primitive values like numbers, strings, or booleans.
The below example shows the execution of the toBe function.
it('should multiply two numbers correctly', () => {
const product : number = 12 * 8;
expect(product).toBe(96);
});
In contrast to toBe, the toEqual matcher is used to perform deep equality comparisons between the actual and expected values. It recursively compares the properties of objects and the elements of arrays to determine if they are equivalent. This makes toEqual suitable for comparing complex data structures like objects and arrays, ensuring that their contents match regardless of reference equality. The below example shows the execution of the toEqual function.
it('should match the object', () =>
{
const user = {
name: 'Rutuparn',
age : 23
};
expect(user).toEqual({ name: 'Rutuparn', age : 23 });
});
In the first version of this section, we are going to test different parameters of an angular project. These parameters are angular components, directives, services, and pipes.
Here, we are testing the component class. Below code snippet below shows the component class.
//example.component.ts
import { Component } from '@angular/core';
@Component({
selector : 'example',
template : `
<button id="btn-1"
(click)='changeBackground($event)'>Change Background
</button>
`,
})
class ExampleComponent {
constructor() {
}
changeBackground(event : any) {
event.target.background = "red";
}
}
Now, if we click on the button, the background color of that button should change to red. To test this scenario, below testing file below is written.
//example.component.spec.ts
import { TestBed } from '@angular/core/testing';
import { ExampleComponent } from './example.component';
describe('ExampleComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [],
declarations: [ ExampleComponent ],
}).compileComponents();
});
it('should change background', () => {
const fixture = TestBed.createComponent(AppComponent);
const ref = fixture.debugElement.nativeElement.querySelector('#btn-1');
ref.click();
expect(ref.background).toBe('red');
});
});
As we know, an angular directive is used to modify an html element's appearance and behavior. Below javascript code snippet below shows a directive viz. printText. This directive when applied on an element, on clicking it will display the text content of that element in upper case.
//uppercase.directive.ts
import { Directive, ElementRef, HostListener } from '@angular/core';
@Directive({
selector : '[uppercase]'
})
class UppercasetDirective {
constructor(element : ElementRef) {
}
@HostListener('click', ['$event.target'])
print(element : any) {
element.textContent = element.textContent.toUpperCase();
}
}
<!-- app.component.html -->
<div id="div-1" uppercase>Hello world</div>
Below code snippet below is the test file for the above directive.
//uppercase.directive.spec.ts
import {TestBed} from '@angular/core/testing';
import { UppercaseDirective } from './uppercase.directive';
describe('PrintTextDirective', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [UppercaseDirective]
});
});
it('should make text content in upper case', () => {
const fixture = TestBed.createComponent(AppComponent);
const ref = fixture.debugElement.nativeElement.querySelector('#btn-1');
const text = ref.textContent;
ref.click();
expect(text.toUpperCase()).toBe(ref.textContent);
});
});
An angular service is nothing but a class where we write non-UI. code and logic that can be reused across an application. Below code snippet shows a service class, in which a function viz. getStringCount will return the total number of characters present in the input string provided to it as a parameter.
// example.service.ts
import { Injectable } from '@angular/core';
@Injectable({
})
class ExampleServive {
constructor() {}
getStringCount(str : string) : number {
return str.length ;
}
}
// app.component.ts
import { Component } from '@angular/core';
import { ExampleServive } from './example.service';
@Component({
selector : 'app',
providers : [ ExampleService ]
})
class AppComponent {
num !: number;
constructor(private exampleService : ExampleService) {}
setNumValue() : void {
this.num = this.exampleService.getStringCount('Hello World');
}
}
Below code snippet below is the test file for the above service.
// example.service.spec.ts
import { TestBed } from '@angular/core/testing';
import { ExampleServive } from './example.service';
describe('DataService', () => {
let service: ExampleServive;
beforeEach(() => {
TestBed.configureTestingModule({
imports: []
});
service = TestBed.get(DataService);
it('should return total characters count', () => {
const str = "Welcome Home";
expect(service.getStringCount(str)).toBe(12);
});
});
});
Angular pipes are classes with @Pipe decorator. Pipes are very useful in angular projects as they tend to modify an element's textContent into desired requirements. Below code snippet below shows an angular pipe.
// example.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name : 'toLowerCase'
})
export class LowerCasePipe implements PipeTransform {
transform(value : any) {
return value.toString().toLowerCase();
}
}
<!-- app.component.html -->
<div>{{ message | toLowerCase }}</div>
Below code snippet below is the test file for the above pipe.
// example.pipe.spec.ts
import { LowerCasePipe } from './example.pipe';
describe('LowerCasePipe', () => {
it('should return text shorter or equal to limit without ellipses', ()
=> {
var pipeInstance = new LowerCasePipe();
let text = 'George';
let result = pipeInstance.transform(text);
expect(result).toBe(text.toLowerCase());
});
});
In this brief overview, we have begun with the importance of testing and the last but not least part i.e. testing of different parameters of an angular application. These parameters were angular components, directives, services, and pipes for convenient web development.
We suggest that every angular developer should learn writing clean unit test cases for their particular angular code. It will not only enhance their testing knowledge but also boost their confidence in writing better and efficient code. These may not be new features but are powerful.
This website uses cookies to analyze website traffic and optimize your website experience. By continuing, you agree to our use of cookies as described in our Privacy Policy.