Mocking the Angular2 Http service for unit tests using karma/jasmine. angular2 , typescript , import , json https://octoperf.com/blog/2016/04/05/angular2-typescript-http-unit-testing/ OctoPerf ZI Les Paluds, 276 Avenue du Douard, 13400 Aubagne, France +334 42 84 12 59 contact@octoperf.com Development 604 2021-01-04

Angular2: hard time unit testing Http requests

OctoPerf is JMeter on steroids!
Schedule a Demo

To follow up on my article about 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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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:

1
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.

Angular2: hard time unit testing Http requests
Tags:
Share:

Be the first to comment

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

Want to become a super load tester?
OctoPerf Superman