Angular HTTP Request Testing

Http Request testing

HttpClient is introduced in Angular 6 and it will help us fetch external data, post to it, etc. We need to import the http module to make use of the http service.

To start using the http service, we need to import the module in app.module.ts as shown below −

import { HttpClientModule } from '@angular/common/http';

imports: [
      HttpClientModule
   ],

HttpClientTestingModule  is used to test HTTP request with HttpTestingController. We can import these from testing package.

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

Request Service File:

import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class RequestService {
  private getUserURL = 'https://jsonplaceholder.typicode.com/users';
  private postUserURL = 'https://jsonplaceholder.typicode.com/posts';

  constructor(private http: HttpClient) { } 

  getUserHttpRequest() {
    return this.http.get(this.getUserURL);
  }

  postUserHttpRequest(payload) {
    const header = new HttpHeaders();
    header.set('Content-type', 'application/json; charset=UTF-8');
    return this.http.post(this.postUserURL, payload, { headers: header })
      .pipe(
        tap(response => this.formatResponse(response)), 
          catchError(this.handleError())
        )
  }

  // Can use tap operator if U have to transform response in other format else can remove above tap operator
  private formatResponse(response) {
    const obj = {
      status: 200,
      statusText: 'OK',
      response: response.id
    }
    return response['response'] = obj;
  }

  private handleError() {
    return (error: HttpErrorResponse): Observable<any> => {
      return of(error);
    };
  }
}

TEST CASES

File name > request.service.spec.ts

import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { async, TestBed } from '@angular/core/testing';

import { RequestService } from './request.service';

describe('RequestService', () => {
  let service: RequestService;
  let httpTestingController: HttpTestingController;
  beforeEach(async(() => TestBed.configureTestingModule({
    imports: [HttpClientTestingModule]
  })));

  beforeEach(() => {
    service = TestBed.get(RequestService);
    httpTestingController = TestBed.get(HttpTestingController);
     /**
     * If you are using Angular Latest Version 9 then instead of TestBed.get we have to use TestBed.inject
     * 
     *  service = TestBed.inject(RequestService);
        httpTestingController = TestBed.inject(HttpTestingController);
     */
  });

  // Instead of check verify in each spec we can simply write in afterEach function.
  afterEach(() => {
    httpTestingController.verify();
  });


  it('should be created', () => {
    const service: RequestService = TestBed.get(RequestService);
    expect(service).toBeTruthy();
  });

  it('Test Get HTTP Request with HttpTesting Controller', () => {
    const mockUserList = [
      {
        "id": 1,
        "name": "Leanne Graham",
        "username": "Bret",
        "email": "Sincere@april.biz",
      }
    ];

    service.getUserHttpRequest().subscribe((resp: any) => {
      console.log("mock resp of get request", resp);
      expect(resp.length).toBeGreaterThanOrEqual(1);
      expect(resp).toEqual(mockUserList);
    });

    const request = httpTestingController.expectOne((service as any).getUserURL);
    expect(request.request.method).toBe("GET");
    expect(request.cancelled).toBeFalsy();
    expect(request.request.responseType).toEqual('json');


    /**
     * Using .flush()
      The request captured by the httpTestingController, req, 
      has a flush method on it which takes in whatever response you would like to provide for that request as an argument.
     */
    request.flush(mockUserList);

    /**
     * Verify All Requests are Complete
      Once all req have been provided a response using flush, we can verify that there are no more pending requests after the test.
      We have done this in afterEach function.

      > httpTestingController.verify();
     */

  });

  it('Test Post HTTP Request  with HttpTesting Controller', () => {
    const payload = JSON.stringify({
      title: 'foo',
      body: 'bar',
      userId: 1
    });

    const mockResult = {
      id: 101
    };
    service.postUserHttpRequest(payload).subscribe(resp => {
      //console.log("mock resp of post request", resp);
      expect(resp).toEqual(mockResult);
    });

    const request = httpTestingController.expectOne((service as any).postUserURL);
    expect(request.request.method).toBe("POST");
    expect(request.request.body).toEqual(payload);
    expect(request.request.url).toMatch((service as any).postUserURL);

    /**
     * flush(body, opts): void 
     * The flush is used to resolve the request by returning a body. For example,
     * Where body is string, number, object etc. and opts is optional
     */

    request.flush(mockResult);

  });

  it('Test Post HTTP Request  with HttpTesting Controller with 500 ERROR', () => {
    service.postUserHttpRequest({}).subscribe(resp => {
      console.log("post request mock error", resp);
      expect(resp.status).toEqual(500);
    });
    const request = httpTestingController.expectOne((service as any).postUserURL);
    const msg = '500';
    request.flush(msg, { status: 500, statusText: 'Server error' });

  });
});

Let’s Understand Above Code :

Access Private variables of Class file into SPEC file.

const service: RequestService = TestBed.get(RequestService);
const postURL = (service as any).postUserURL;  


We can get component instance & Native elements with below two ways.

 const fixture = TestBed.createComponent(AppComponent);

// Two ways to get component Instance
 const app = fixture.componentInstance;
 const app1 = fixture.debugElement.componentInstance;

// Two ways you can get native elements.
const compiled = fixture.debugElement.nativeElement;
const compiled1 = fixture.nativeElement;


Use of fixture.detectChanges();

We run this method to call change detection hook. So If you have to test an HTML element after interpolate a value from TS file so You need to call this method so that first it renders on DOM before testing.


Common Errors when writing test cases of service file:

Error: : Expected a spy, but got Function.
Usage: expect().toHaveBeenCalled()

TypeError: Cannot read property ‘subscribe’ of undefined

NullInjectorError: StaticInjectorError(DynamicTestModule)[HttpClient]:
StaticInjectorError(Platform: core)[HttpClient]:
NullInjectorError: No provider for HttpClient!

Read here about JASMINE framework : https://jasmine.github.io/2.5/introduction

Read here: Dynamic Tooltip with Angular Pipe: Performance Orientation https://www.jsmount.com/dynamic-tooltip-with-angular-pipe/

Angular HTTP Request Testing

  1. It’s like you learn my mind! You appear to understand so much about this, such as you wrote the e-book in it or something.
    I feel that you simply can do with a few % to pressure the message house a bit, but other than that, that is a wonderful blog. An excellent read.
    I will certainly be back.

Comments are closed.