Unit test Http Services on Angular 2

Angular 2 testing documentation is more than good, but I believe that it is missing a crucial bit. How should we test services that make use of Http?

For example, let’s say that we want to unit test a TasksService with a getTasks method to GET task resources from a REST API.

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { Task } from './task';

@Injectable()
export class TasksService {
  private tasksUrl = 'http://a-task-list-api.com/tasks';

  constructor(private http: Http) {}

  getTasks(): Promise<Task[]> {
    return this.http.get(this.tasksUrl)
      .toPromise()
      .then(response => response.json() as Task[]);
  }
}

The service uses angular Http service to perform HTTP requests. Unit test of this service should be done in isolation because we don´t want our service to be hitting the real network and at the same time, we want to control and fake the responses from the API. For that reason, we need to use a mock Http service.

Implementing a fake version of the Http service can get really complicated, the good thing is that Angular 2 already provides one for us, the MockBackend.

This is a full example of unit test using MockBackend and BaseRequestOptions to create a Http mock.

import { TestBed, inject, tick, fakeAsync } from '@angular/core/testing';
import { BaseRequestOptions, Http, ConnectionBackend, Response, ResponseOptions, RequestMethod } from '@angular/http';
import { MockBackend } from '@angular/http/testing';

import { TasksService } from './tasks.service';
import { Task } from './task';

describe('TasksService', () => {

  const TASKS: Task[] = [
    {id: 'task-1', name: 'Buy milk', done: false, userId: 'user-1'},
    {id: 'task-2', name: 'Pay rent', done: true, userId: 'user-1'}
  ];

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        {
          provide: Http,
          deps: [MockBackend, BaseRequestOptions]
          useFactory: (backend: ConnectionBackend, defaultOptions: BaseRequestOptions) => {
          return new Http(backend, defaultOptions);
         }
        },
        {provide: MockBackend, useClass: MockBackend},
        {provide: BaseRequestOptions, useClass: BaseRequestOptions},
        {provide: TasksService, useClass: TasksService}
      ]
    });
  });

  describe('getTasks()', () => {

    it('should return all tasks', inject([TasksService, MockBackend], fakeAsync((tasksService: TasksService, mockBackend: MockBackend) => {
      let result;

      mockBackend.connections.subscribe(c => {
        expect(c.request.method).toBe(RequestMethod.Get);
        expect(c.request.url).toBe('http://paucls-task-list-api.herokuapp.com/tasks');
        let response = new ResponseOptions({body: TASKS});
        c.mockRespond(new Response(response));
      });

      tasksService.getTasks().then(tasks => {
        result = tasks;
      });
      tick();

      expect(result.length).toBe(TASKS.length);
    })));

  });

});

As a reference, in Angular 1 we use the $httpBackend service mock.

    describe('getTasks()', function () {
        it('should return all tasks', function () {
            $httpBackend.expectGET('/tasks').respond(TEST_TASKS);

            let tasks = TasksService.getTasks();
            $httpBackend.flush();
            tasks = resolvePromise(tasks, $q, $scope);

            expect(tasks).toEqual(TEST_TASKS);
        });
    });

Documentation:
https://angular.io/docs/ts/latest/guide/testing.html#!#isolated-service-tests
http://gist.asciidoctor.org/?github-mraible%2Fng2-demo%2F%2FREADME.adoc
https://semaphoreci.com/community/tutorials/testing-angular-2-http-services-with-jasmine

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s