Web Development

Unit Testing in Angular

January 31st, 2023 | By Jay Raj | 10 min read

Unit testing in Angular apps is one of the phases of software development. It helps in adding new enhancements without breaking existing application features.

There are several tools and frameworks for writing and running unit test cases. In this Angular project, we'll see how to write and run test cases using Jasmine and Karma.

How to do unit testing in Angular?


The behavior-driven JavaScript framework Jasmine is the one we will use for writing unit test cases in Angular. It helps in writing unit tests in a readable format that anyone can understand.

We'll be writing unit tests by creating instances of our components and services and by mocking and spying on the methods.

From the official documentation:
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.


Karma is more of a tool that helps in running the test case written using the Jasmine framework and displaying the output in the terminal.

From the official documentation:
A simple tool that allows you to execute JavaScript code in multiple real browsers.


How to get started with Unit Testing in Angular


Let's start by creating an Angular project from scratch. From your terminal, install the Angular CLI.

npm install -g @angular/cli


Once you have the CLI installed, create an Angular project using the following command:

ng new unit-testing-angular


Select Angular routing when asked and select the rest of the configurations. That will create an Angular project with some boilerplate code. Navigate to the project directory and run the application:

cd unit-testing-angular
npm run start


Now let's add some Angular code so that we can write some unit tests. Open your app.component.ts file and add the following code:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'unit-testing-angular';

  public users : User[];

  constructor(private http: HttpClient) {
    this.users = [];
  }

  ngOnInit(): void {
    //Called after the constructor, initializing input properties, and the first call to ngOnChanges.
    //Add 'implements OnInit' to the class.
    this.getData();
  }

  getData(){
    this.http.get('https://jsonplaceholder.typicode.com/users').subscribe({
      next : (response:any) => {
        this.users = response;
      },
      error : (error) =>{
        this.users = [];
      }
    })
  }
}

interface User{
  name: string,
  email: string
}


The above makes use of the HttpClientModule so go to your app.module.ts file and replace it with the following code:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http'

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }


For rendering the application, you need to add the following HTML code to app.component.html:

<div>
  <table>
    <tr>
      <th>
        Name
      </th>
      <th>
        Email
      </th>
    </tr>
    <tr *ngFor="let user of users">
      <td>
        {{user.name}}
      </td>
      <td>
        {{user.email}}
      </td>
    </tr>
  </table>
</div>


Save your changes, and you will have a list of names and emails listed on your application page. Now let's write our first Angular unit test case.

Writing Your First Unit Test Case

For your first test, you don't need to install anything. Jasmine and Karma have already been installed, along with some boilerplate code to get started.

Inside your src/app folder where you have the app.component.html file, you will also have the app.component.spec.ts file. That is the unit testing file for your AppComponent.

import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  });

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

  it(`should have as title 'unit-testing-angular'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.title).toEqual('unit-testing-angular');
  });

  it('should render title', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.nativeElement as HTMLElement;
    expect(compiled.querySelector('.content span')?.textContent).toContain('unit-testing-angular app is running!');
  });
});


Let's try to understand the above code.

The first thing that you'll notice is that you have a couple of imports like TestBed, RouterTestingModule in the spec file.

While running a unit test case for a component, you first need to configure it using whatever imports and providers the component requires. For that, you make use of the TestBed module. As seen from the code, you are making use of TestBed.configureTestingModule to configure the test bed.

RouterTestingModule is used for testing the app routing and other related use cases, which we can omit for the time being.

You will also notice it and describe keywords in the spec file. A unit test case or a specification is written by using the keyword it. The describe keyword is used to group a number of specs.

Our app code makes use of the HttpClientModule to make API calls. So you need to import it into the spec file.

import { HttpClientTestingModule } from  '@angular/common/http/testing';


Add it to the imports inside the testbed configuration. Also, remove the last two test cases, and let's just work out the first test case. Here is how the modified spec file looks:

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule
      ],
      declarations: [
        AppComponent
      ],
    }).compileComponents();
  });

  it('should create the app', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app).toBeTruthy();
  });

});


Before running the unit test case, let's just look at our first test case. In the above test case, we expect the app to be initialized properly. For that, we are creating an instance of our AppComponent. If that app instance is created fine, it means it's getting initialized fine.

For getting an instance of the component, we are making use of the TestBed.createComponent method, where we are passing in the component to create.

From the returned response, we are getting the component instance, which we are checking to be truthful.

Save the above changes and run the unit test case.

npm run test


Once the test case has executed fine, you will get the following response in your terminal:

Chrome 108.0.0.0 (Windows 10): Executed 1 of 1 SUCCESS (0.072 secs / 0.054 secs)    
TOTAL: 1 SUCCESS


So you succeeded in writing your first unit test, or, to be precise, understanding a simple pre-written test case. Now let's move forward and write some unit tests for the existing Angular component.

Testing Angular component

Let's first write some code for us to unit test. I have added some code to get data from an API endpoint and process the data to show the Name, email, and City in the UI. Add the following code to the app.component.ts file:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  public users : User[];

  constructor(private http: HttpClient) {
    this.users = [];
  }

  ngOnInit(): void {
    //Called after the constructor, initializing input properties, and the first call to ngOnChanges.
    //Add 'implements OnInit' to the class.
    this.getData();
  }

  getData(){
    this.http.get('https://jsonplaceholder.typicode.com/users').subscribe({
      next : (response:any) => {
        if(response && response.length){
          this.users = response.map((u:any) => {
            u['city'] = this.getCity(u['address']);
            return u;
          });
        } else {
          this.users = [];
        }
      },
      error : (error) =>{
        this.users = [];
      }
    })
  }

  getCity(address:any){
    if(address.city){
      return `Residing at ${address.city}`
    }
    return "No city specified";
  }
}
interface User{
  name: string,
  email: string,
  city: string
}


In the ngOnInit, we are calling the getData method, which makes the API call, and after parsing the city using the getCity method, we are filling the user's array.

As you noticed, we have two methods in our components: getData and getCity. Let's write a unit test for testing the method getCity.

Now, this method GetCity has two scenarios that need to be covered. One when the address has a city, and one when it does have a city.

We'll create a component, and using its instance, we'll invoke the getCity method and validate its response.

it(`should return the city'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.getCity({city : "Delhi"})).toEqual(`Residing at Delhi`);
  });

  it(`should return the city not found'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    expect(app.getCity({})).toEqual(`No city specified`);
  });


We wrote two test cases, one for an address with a city and one without a city. In both test cases, we are comparing the responses if appropriate to validate the test case. Save the above changes and run the test case.

npm run test


You will be able to see a message that the test cases ran successfully.

Chrome 108.0.0.0 (Windows 10): Executed 3 of 3 SUCCESS (0.126 secs / 0.055 secs)
TOTAL: 3 SUCCESS


Now let's move forward and write another test case to cover the getData method.

The getData method uses observables or subscriptions while making API calls. Let's learn how to write unit tests for testing methods with a subscription.

Testing Subscriptions

In the existing code, it's a bit difficult to mock the API call since the subscription code and the logic are coupled. So while writing code, make sure to divide your code into small logical units which makes it easier to unit test.

I'll break down the existing code getData into two separate methods. For that create an Angular service using the following code:

ng g service data


The above command will create a .spec file for DataService. You can delete it since we'll be focusing only on the app.component.spec file.

Add the following code to the data.service.ts file:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class DataService {

  constructor(private http: HttpClient) { }

  fetchUsersData(){
    return this.http.get('https://jsonplaceholder.typicode.com/users')
  }
}


Here is the modified app.component.ts file:

import { Component } from '@angular/core';
import { DataService } from './data.service';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  public users : User[];

  constructor(private dataService: DataService) {
    this.users = [];
  }

  ngOnInit(): void {
    //Called after the constructor, initializing input properties, and the first call to ngOnChanges.
    //Add 'implements OnInit' to the class.
    this.getData();
  }

  getData(){
    this.dataService.fetchUsersData().subscribe({
      next : (response:any) => {
        if(response && response.length){
          this.users = response.map((u:any) => {
            u['city'] = this.getCity(u['address']);
            return u;
          });
        } else {
          this.users = [];
        }
      },
      error : (error) =>{
        this.users = [];
      }
    })
  }

  getCity(address:any){
    if(address.city){
      return `Residing at ${address.city}`
    }
    return "No city specified";
  }
}
interface User{
  name: string,
  email: string,
  city: string
}


Let's write a test case to unit test getData. getData makes a call to fetchUsersData which returns an observable. So instead of making an actual API call via fetchUsersData, we'll mock the call to fetchUsersData.

Let's start by defining the test case and creating a reference to the AppComponent.

  it(`should return empty list of users'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
  });


For mocking you can make use of spyOn. Start by importing the DataService.

import { DataService } from './data.service';


For using DataService you first need to create a reference to the service DataService

  it(`should return empty list of users'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    let service = fixture.debugElement.injector.get(DataService);
  });


Using the service reference you can set up a mock call using the callFake method as shown in the below code.

    it(`should return empty list of users'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    let service = fixture.debugElement.injector.get(DataService);
    spyOn(service,"fetchUsersData").and.callFake(() => {
      return of([]);
    });
    app.getData();
    expect(app.users).toEqual([]);
  });


In the above code, we are mocking the fetchUsersData method to return an empty list. Once the mock has been set we are calling the getData method and validate the output.

Save the above changes and start the unit tests.

npm run test


Similarly, let us add one more test case with some data instead of an empty array.

    it(`should return list of users'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.componentInstance;
    let service = fixture.debugElement.injector.get(DataService);
    spyOn(service,"fetchUsersData").and.callFake(() => {
      return of([{city:"Delhi",address:"New Delhi"}]);
    });
    app.getData();
    expect(app.users.length).toEqual(1);
  });


In the above test case, I'm passing in some data and expecting the user length to be one. That's how you can unit-test observables.

Wrapping It up

In this tutorial, you learned how to get started with writing unit test cases for your Angular application.

You learned how to make use of spyOn and callFake while testing subscriptions and observables.

You can further explore different aspects of Angular unit testing by visiting the official documentation.

The source code from this tutorial is available on GitHub.

Jscrambler

The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.

View All Articles

Must read next

Javascript

Getting Started with Angular 2 End To End Testing

In this article, we will be testing an existing Angular 2 todo application. We will be using integration tests and will cover several scenarios.

October 12, 2016 | By Lamin Sanneh | 10 min read

Web Development

Getting Started with Observables in Angular

Let's put Angular Observables under the microscope - understanding how they handle data streams and seeing them in action in some example scenarios!

November 27, 2019 | By Ajdin Imsirovic | 6 min read

Section Divider