Angular2: Hard Time Unit Testing Http Requests

Angular2: Hard Time Unit Testing Http Requests

To follow up on my article about unit TypeScript generics in Angular2, I would like to unit test my Stripe client.

It involves mocking the Angular2 Http service, and it’s far more complicated than unit testing the Router service. I first tried to inject a mock of the Http service and return custom Observable responses but this led to strange errors and cumbersome code.

I quickly switched to the recommended way, using MockBackend.

The service to test

As a remainder, the service tested is a Stripe client. It makes recursive Http request to Stripe API in order to fetch customers and plans:

import {Injectable} from 'angular2/core';
import {Http, Headers, URLSearchParams} from 'angular2/http';
import {Customer} from './Customer';
import {Plan} from './Plan';
import {PaginatedList} from './PaginatedList';
import {HasId} from './HasId';

const stripeUrl: string = 'https://api.stripe.com/v1';

@Injectable()
export class StripeClient {
  private apiKey: string = '';

  constructor(private http: Http) {
  }

  getApiKey(): string {
    return this.apiKey;
  }

  setApiKey(apiKey: string) {
    this.apiKey = apiKey;
  }

  getCustomers(callback: (customers: PaginatedList<Customer>)=>boolean) {
    this.getData('/customers', null, callback);
  }

  getPlans(callback: (plans: PaginatedList<Plan>)=>boolean) {
    this.getData('/plans', null, callback);
  }

  private getData<T extends HasId> (endpoint: string, starting_after: string, callback: (data: PaginatedList<T>)=>boolean) {
    const params: URLSearchParams = new URLSearchParams();
    params.set('limit', '100');
    if (starting_after){
      params.set('starting_after', starting_after);
    }

    this.http.get(stripeUrl + endpoint, {headers: this.getAuthHeaders(), search: params})
      .map(res => res.json())
      .subscribe((paginatedList: PaginatedList<T>) => {
        if (callback(paginatedList) && paginatedList.has_more){
          this.getData(endpoint, paginatedList.data[paginatedList.data.length - 1].id, callback);
        }
      });
  }

  private getAuthHeaders(): Headers {
    var headers = new Headers();
    headers.append('Authorization', 'Bearer ' + this.apiKey);
    return headers;
  }
}

The unit test

The idea is to replace the standard XHRBackend my the mock one: MockBackend.

This mock backend provides methods and fields to handle incoming requests:

import {
  it,
  inject,
  injectAsync,
  beforeEachProviders
} from 'angular2/testing';
import {HTTP_PROVIDERS, XHRBackend, Response, ResponseOptions} from 'angular2/http';
import {provide} from 'angular2/core';
import {MockBackend} from 'angular2/http/testing';
import {MockConnection} from 'angular2/src/http/backends/mock_backend';
import {Plan} from './Plan';
import {PaginatedList} from './PaginatedList';
import {StripeClient} from './StripeClient';
import 'rxjs/Rx';

describe('StripeClient', () => {
  beforeEachProviders(() => [
    HTTP_PROVIDERS,
    provide(XHRBackend, {useClass: MockBackend}),
    StripeClient
  ]);

  it('should set and get secret key', inject([StripeClient], (client) => {
    client.setApiKey('test');
    expect(client.getApiKey()).toEqual('test');
  }));

  it('should get plans', inject([XHRBackend, StripeClient], (mockBackend, client) => {
    mockBackend.connections.subscribe(
      (connection: MockConnection) => {
        connection.mockRespond(new Response(
          new ResponseOptions({
              body: JSON.stringify(
                {
                  has_more: false,
                  data: [{
                    name:'name',
                    id:'id'
                  }]
                }
              )
            }
          )));
      });

    var plans: Plan[] = [];
    var has_more: boolean;

    client.getPlans((_plans: PaginatedList<Plan>) => {
      plans = plans.concat(_plans.data);
      has_more = _plans.has_more;
    });

    expect(plans.length).toBe(1);
    expect(has_more).toBe(false);
  }));

});

This test is far from perfect.

First of all I have to import some Rx stuff (import 'rxjs/Rx';) to avoid the following error:

Failed: undefined is not a function (evaluating 'this.http.get(stripeUrl+endpoint,{headers:this.getAuthHeaders(),search:params}).map(function(res){__cov_7Fe_TqajXZVQEOBsQl5hYg.f['10']++;__cov_7Fe_TqajXZVQEOBsQl5hYg.s['34']++;return res.json();})')

The map method from Observale is not recognized without this import.

Then I could not find a way to mock the successive calls made against Stripe API. I cannot subscribe to multiple urls by doing several calls to mockBackend.connections.subscribe. It’s still a beta version / work in progress and we are far from the ease of use offered by AngularJS $httpBackend mock.

By - CEO.
Tags: Angular2 Typescript Import Json

Comments

 

Thank you

Your comment has been submitted and will be published once it has been approved.

OK

OOPS!

Your post has failed. Please return to the page and try again. Thank You!

OK