Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test Angular2 service with mock backend

First: I'm aware that Angular2 is in alpha and changing frequently.

I'm working with Angular2. There is an injectable service with http dependency that I'd like to test using a mock backend. The service works when the app starts but I'm having no luck writing the test and getting the mock backend to respond. Any insight, is there something obvious in the test setup or implementation that I'm missing?

service/core.ts:

import { Injectable } from 'angular2/angular2';
import { Http } from 'angular2/http';

@Injectable()
export class CoreService {

    constructor(public http:Http) {}

    getStatus() {
        return this.http.get('/api/status')
            .toRx()
            .map(res => res.json());
    }
}

service/core_spec.ts:

import {
    AsyncTestCompleter,
    TestComponentBuilder,
    By,
    beforeEach,
    ddescribe,
    describe,
    el,
    expect,
    iit,
    inject,
    it,
    xit
} from 'angular2/test';
import { MockBackend, MockConnection, BaseRequestOptions, Http, Response } from 'angular2/http';
import { Injector, bind } from 'angular2/angular2';
import { ObservableWrapper } from 'angular2/src/core/facade/async'

import { CoreService } from 'public/services/core'

export function main() {

    describe('public/services/core', () => {

        let backend: MockBackend;
        let response: Response;
        let coreService: CoreService;
        let injector: Injector;

        afterEach(() => backend.verifyNoPendingRequests());

        it('should get status', inject([AsyncTestCompleter], (async) => {

            injector = Injector.resolveAndCreate([
                BaseRequestOptions,
                MockBackend,
                bind(Http).toFactory((backend, options) => {
                    return new Http(backend, options)
                }, [MockBackend, BaseRequestOptions]),
                bind(CoreService).toFactory((http) => {
                    return new CoreService(http);
                }, [Http])
            ]);

            backend = injector.get(MockBackend);
            coreService = injector.get(CoreService);
            response = new Response('foo');

            ObservableWrapper.subscribe<MockConnection>(backend.connections, c => {
                expect(c.request.url).toBe('/api/status');
                c.mockRespond(response);
            });

            // attempt #1: fails because res argument is undefined
            coreService.getStatus().subscribe(res => {
                expect(res).toBe('');
                async.done();
            });

            // attempt #2: fails because emitter.observer is not a function
            ObservableWrapper.subscribe(coreService.getStatus(), res => {
                expect(res).toBe('');
                async.done();
            });

        }));
    });

}

Related: https://github.com/angular/angular/issues/3502 https://github.com/angular/angular/issues/3530

like image 423
stealththeninja Avatar asked Sep 28 '15 18:09

stealththeninja


People also ask

How do I mock an Angular service?

To test a service, you set the providers metadata property with an array of the services that you'll test or mock. content_copy let service: ValueService; beforeEach(() => { TestBed. configureTestingModule({ providers: [ValueService] }); }); Then inject it inside a test by calling TestBed.

How do you unit test a service with a dependency?

Service Dependencies As soon as you add even one dependency to your service, you need to also add it to your tests. In case of isolated tests, you will need to pass an instance of an injectable dependency class into the constructor of your service instantiation.


2 Answers

I just found this topic while looking for testing tips but I can't see a direct answer to that so...

This one is based on Angular RC.1

Testing service with Mock Backend

Let's say your service is:

import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

@Injectable()
export class CoreService {
  constructor(private http: Http) {}

  getStatus() {
    return this.http.get('/api/status');
  }
}

Test to the service above will look like this:

import {
  beforeEach,
  beforeEachProviders,
  describe,
  expect,
  inject,
  it,
} from '@angular/core/testing';

import { provide } from '@angular/core';
import { BaseRequestOptions, Response, ResponseOptions } from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';

describe('Http', () => {

  beforeEachProviders(() => [
    CoreService,
    BaseRequestOptions,
    MockBackend,
    provide(Http, {
      useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => {
        return new Http(backend, defaultOptions);
      },
      deps: [MockBackend, BaseRequestOptions]
    })
  ]);

  beforeEach(inject([MockBackend], (backend: MockBackend) => {
    const baseResponse = new Response(new ResponseOptions({ body: 'status' }));
    backend.connections.subscribe((c: MockConnection) => c.mockRespond(baseResponse));
  }));

  it('should return response when subscribed to getStatus',
    inject([CoreService], (coreService: CoreService) => {
      coreService.getStatus().subscribe((res: Response) => {
        expect(res.text()).toBe('status');
      });
    })
  );

})

What you really have to look at there is to have proper mocking in beforeEachProviders. Test itself is quite simple and ends up with subscribing to the service method.


Note: Don't forget to set base providers first:

import { setBaseTestProviders } from '@angular/core/testing';
import {
  TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS,
  TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
} from '@angular/platform-browser-dynamic/testing';

setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
like image 94
Wojciech Kwiatek Avatar answered Oct 12 '22 02:10

Wojciech Kwiatek


Since asking this question we did upgrade to Angular2 RC 1. Our imports look like Wojciech Kwiatek's (thank you for your answer!) but our testing strategy is slightly different. We wanted to assert on the request as well as the response. Instead of using beforeEachProviders(), we used beforeEach() where we create our own injector and save a reference to the service-under-test and mock backend. This allows us to assert on the request and manage the response inside the test, and it lets us use the verifyNoPendingRequests() method after each test.

describe('core-service', () => {

  let service: CoreService;
  let backend: MockBackend;

  beforeEach(() => {
    injector = ReflectiveInjector.resolveAndCreate(<any> [
        CoreService,
        BaseRequestOptions,
        MockBackend,
        provide(Http, {
            useFactory: (mockBackend, defaultOptions) => new Http(mockBackend, defaultOptions),
            deps: [MockBackend, BaseRequestOptions]
        })
    ]);

    service = <CoreService> injector.get(CoreService);
    backend = <MockBackend> injector.get(MockBackend);
  });

  afterEach(() => backend.verifyNoPendingRequests());

  it('should get status', () => {

    backend.connections.subscribe((c: MockConnection) => {
      expect(c.request.url).toEqual('api/status');
      c.mockRespond(new Response(new ResponseOptions({ body: 'all is well' })));
    });

    service.getStatus().subscribe((status) => {
      expect(status).toEqual('all is well');
    });

  }));
});

Edit: Plunker updated to RC2. https://plnkr.co/edit/nlvUZVhKEr8d2mz8KQah?p=preview

like image 41
stealththeninja Avatar answered Oct 12 '22 00:10

stealththeninja