Unit Testing Promise.all in Angular | Karma | Jasmine


Unit Testing Promise.all in Angular. Unit testing asynchronous code in Angular.

{% include youtube_embed.html id=“I3Y4ybVYynw” %}

Let’s say, this is your component code,

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { CommonService } from './common.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {

  public firstAPIresponse:any;
  public secondAPIresponse:any;
  public thirdAPIresponse:any;

  public errorMessage:any;

  constructor(private http : HttpClient, private service : CommonService){}

  ngOnInit(){
    this.makeAPICall();
  }

  makeAPICall(){
    Promise.all([this.service.firstAPICall(), this.service.secondAPICall(),this.service.thirdAPICall()])
      .then((response) => {
        this.parseResponse(response);
      })
      .catch((error) => {
        this.errorMessage = error;
      })
  }

  parseResponse(response : any){
    if(!response || !Array.isArray(response)) return;
    this.firstAPIresponse = response[0] ? response[0] : [];
    this.secondAPIresponse = response[1] ? response[1] : [];
    this.thirdAPIresponse = response[2] ? response[2] : [];
  }

}

makeAPICall is using Promise.all which waits for the three API calls to finish. Once the promises inside Promise.all resolves successfully, the callback function is executed which calls the parseResponse method. If any of the promises rejects, the catch callback is executed.

You can make use of spyOn to mock the service calls and return promise.

let firstAPICall = spyOn(service,"firstAPICall").and.resolveTo([]);
let secondAPICall = spyOn(service,"secondAPICall").and.resolveTo([]);
let thirdAPICall = spyOn(service,"thirdAPICall").and.resolveTo([]);

With the service mocks, here is how the unit test case looks like:

  it('should call makeAPICall', () => {
    const fixture = TestBed.createComponent(AppComponent);
    const component = fixture.componentInstance;
    let service = fixture.debugElement.injector.get(CommonService);
    let firstAPICall = spyOn(service,"firstAPICall").and.resolveTo([]);
    let secondAPICall = spyOn(service,"secondAPICall").and.resolveTo([]);
    let thirdAPICall = spyOn(service,"thirdAPICall").and.resolveTo([]);
    component.makeAPICall();
    expect(component.firstAPIresponse).toEqual([]);
  })

If you try running the above, code it won’t pass since the unit test would run before the asynchronous promise execution.

To fix it you’ll also need to use fakeAsync and tick to test asynchronous code. From the official documentation,

fakeAsync Wraps a function to be executed in the fakeAsync zone:

Here is the unit test case using fakeAsync.

  it('should call makeAPICall', fakeAsync( () => {
    const fixture = TestBed.createComponent(AppComponent);
    const component = fixture.componentInstance;
    let service = fixture.debugElement.injector.get(CommonService);
    let firstAPICall = spyOn(service,"firstAPICall").and.resolveTo([]);
    let secondAPICall = spyOn(service,"secondAPICall").and.resolveTo([]);
    let thirdAPICall = spyOn(service,"thirdAPICall").and.resolveTo([]);
    component.makeAPICall();
    tick();
    expect(component.firstAPIresponse).toEqual([]);
  }))

When the above code runs inside the fakeAsync zone, all asynchronous code is queued in.

tick method triggers the execution of all asynchronous code and hence the above test case works fine and passes successfully.

So, this is how you can unit test promise.all or asynchronous code in your Angular application.