Egor Panok

Full Stack JavaScript Developer

Unit Testing of Angular components which use HTTP requests

January 29, 2019 - 5 min read

Angular

TL;DR: To test Angular components which themselves ultimately use http requests we can leverage Jasmine spies

Use Case

Let’s suppose, we have a UserService with getAll() method requesting the https://jsonplaceholder.typicode.com/users endpoint and UserListComponent meant to show that users. Our objective is to test whether the component calls this service’s method and whether the component’s receive that users.

UserService

That’s how the UserService looks like:

// user.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { IUser } from './models/user.model';

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

  basicUrl = 'https://jsonplaceholder.typicode.com';

  constructor(
    private _httpClient: HttpClient
  ) { }

  getAll(): Observable<IUser[]> {
    return this._httpClient.get<IUser[]>(`${this.basicUrl}/users`);
  }
}

Here are the models:

// address.model.ts

export interface IAddress {
    street: string;
    suite: string;
    city: string;
    zipcode: string;
}

// company.model.ts

export interface ICompany {
    name: string;
    catchPhrase: string;
    bs: string;
}

// user.model.ts

export interface ICompany {
    name: string;
    catchPhrase: string;
    bs: string;
}

UserListComponent

UserListComponent just uses the injected service and stores the Observable in its users$ property:

// user-list.component.ts

import { Component, OnInit } from '@angular/core';
import { UserService } from '../../../services/user.service';
import { IUser } from '../../../services/models/user.model';
import {Observable} from 'rxjs';

@Component({
  selector: 'app-user-list',
  templateUrl: './user-list.component.html',
  styleUrls: ['./user-list.component.scss']
})
export class UserListComponent implements OnInit {

  users$: Observable<IUser[]>;

  constructor(
    private _userService: UserService
  ) { }

  ngOnInit() {
    this.users$ = this._userService.getAll();
  }

}

The component’s template just shows the users. Here I use Bulma.io for a quick and nice styling:

// user-list.component.html
<div>
    <div *ngFor="let user of users$ | async" class="user-card">
        <p class="title is-5">{{user.name}}</p>
        <p class="subtitle is-6">{{user.company.name}}</p>
    </div>
</div>

Testing UserListComponent

To start testing UserListComponent, we have to import the TestBed, HttpClientTestingModule, ComponentFixture along with our domain specific dependency - UserService and auxiliaries to work with Observables.

// user-list.component.spec.ts

import { async, ComponentFixture, TestBed } from "@angular/core/testing"
import { HttpClientTestingModule } from "@angular/common/http/testing"
import { UserListComponent } from "./user-list.component"
import { UserService } from "../../../services/user.service"
import { Observable } from "rxjs"
import "rxjs/add/observable/of"

Then let’s define beforeEach section to initialize a proper isolated testing environment for our tests.

Here are the key things to do inside the beforeEach section:

  1. Configuring the TestBed
TestBed.configureTestingModule({
    imports: [HttpClientTestingModule],
    declarations: [UserListComponent],
    providers: [UserService]
}).compileComponents()
  1. Creating a component, getting its fixture and the injected UserService
// Creating a component and getting the fixture
fixture = TestBed.createComponent(UserListComponent)

// Getting the instance of component out of the fixture
component = fixture.componentInstance

// Getting the UserService from the component's injector
userService = fixture.debugElement.injector.get(UserService)
  1. Creating mock users and configuring a Jasmine’s spy to watch calling UserService.getAll() method and returning an Observable of the mock users when it happens.
mockUsers = [...];
spy = spyOn(userService, 'getAll').and.returnValue(Observable.of(mockUsers));
fixture.detectChanges();

Here is the complete listing of the beforeEach part:

// user-list.component.spec.ts

describe("UserListComponent", () => {
    let component: UserListComponent
    let fixture: ComponentFixture<UserListComponent>
    let userService: UserService
    let spy: jasmine.Spy
    let mockUsers

    beforeEach(async(() => {
        // Configuring the TestBed
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            declarations: [UserListComponent],
            providers: [UserService]
        }).compileComponents()

        // Creating a component and getting the fixture
        fixture = TestBed.createComponent(UserListComponent)
        // Getting the instance of component out of the fixture
        component = fixture.componentInstance
        // Getting the UserService from the component's injector
        userService = fixture.debugElement.injector.get(UserService)

        mockUsers = [
            {
                id: 1,
                name: "Leanne Graham",
                username: "Bret",
                email: "Sincere@april.biz",
                address: {
                    street: "Kulas Light",
                    suite: "Apt. 556",
                    city: "Gwenborough",
                    zipcode: "92998-3874"
                },
                phone: "1-770-736-8031 x56442",
                website: "hildegard.org",
                company: {
                    name: "Romaguera-Crona",
                    catchPhrase: "Multi-layered client-server neural-net",
                    bs: "harness real-time e-markets"
                }
            },
            {
                id: 2,
                name: "Ervin Howell",
                username: "Antonette",
                email: "Shanna@melissa.tv",
                address: {
                    street: "Victor Plains",
                    suite: "Suite 879",
                    city: "Wisokyburgh",
                    zipcode: "90566-7771"
                },
                phone: "010-692-6593 x09125",
                website: "anastasia.net",
                company: {
                    name: "Deckow-Crist",
                    catchPhrase: "Proactive didactic contingency",
                    bs: "synergize scalable supply-chains"
                }
            }
        ]
        spy = spyOn(userService, "getAll").and.returnValue(
            Observable.of(mockUsers)
        )
        fixture.detectChanges()
    }))
})

Alright, now we’re ready to write the tests:

// user-list.component.spec.ts

describe("UserListComponent", () => {
    let component: UserListComponent
    let fixture: ComponentFixture<UserListComponent>
    let userService: UserService
    let spy: jasmine.Spy
    let mockUsers

    // beforeEach section is omitted here

    it("should create", () => {
        expect(component).toBeTruthy()
    })

    it("should call UserService", () => {
        expect(spy.calls.any()).toBeTruthy()
    })

    it("should set userList", () => {
        component.users$.subscribe(users => {
            expect(users).toEqual(mockUsers)
        })
    })
})

The first test just verifies that the component was created. The second one relies on the spy to check if the component called the UserService. The last one verifies if the users$ property acquired the mockUsers value.

It’s worth to point out that instead of the Jasmine spies we could have used HttpTestingController like in the previous article.

Summary

To test Angular components which ultimately leverage the http requests we can use Jasmine spies, which intercept the request and return the required mock data.

Feel free to check out the repository.

Happy coding!


Written by Egor Panok, full stack JavaScript Developer who loves building useful things. Follow him on Twitter

© 2020, Egor Panok, Full Stack JavaScript Developer