Trying out Angular2 service using a fabricated backend

Just a heads up: I know Angular2 is still in alpha and undergoing frequent changes.

I am currently working with Angular2 and facing an issue with testing an injectable service that has a dependency on http. I want to test this service using a mock backend. The service functions correctly when the application starts, but I am struggling with writing the test and getting the mock backend to provide a response. Is there something obvious in the test setup or implementation that I might be overlooking?

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

Answer №1

As I was searching for testing tips, I stumbled upon this topic but couldn't find a direct answer.

This particular approach is centered around Angular RC.1

Testing service with Mock Backend

Let's assume your service looks like this:

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');
  }
}

A test for the service above would resemble 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');
      });
    })
  );

})

Focus on having proper mocking in beforeEachProviders. The test itself is straightforward and involves subscribing to the service method.


Note: Make sure 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);

Answer №2

After posing this inquiry, we underwent an upgrade to Angular2 RC 1. Our import setup mirrors Wojciech Kwiatek's (gratitude for the guidance!) with a slight deviation in our testing approach. Our aim was to validate both the request and response. Instead of utilizing beforeEachProviders(), we opted for beforeEach() where we establish our custom injector, retain a reference to the service being tested, and the mock backend. This empowers us to assert on the request, handle the response within the test, and utilize the verifyNoPendingRequests() method following 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');
    });

  }));
});

Update: Plunker has been revised to RC2. https://plnkr.co/edit/nlvUZVhKEr8d2mz8KQah?p=preview

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Error in Angular 2: Component unable to locate imported module

I'm facing an issue where a module I want to use in my application cannot be found. The error message I receive is: GET http://product-admin.dev/node_modules/angular2-toaster/ 404 (Not Found) The module was installed via NPM and its Github reposito ...

Dealing with a series of challenging npm problems consecutively

npm WARNING: Deprecated Usage Please note that global '--global' and '--local' configurations are now deprecated. Please use '--location=global' instead. Error Code: ERESOLVE Unable to resolve dependency tree. While reso ...

Angular: Streamlining the Constructor Function for Efficiency

Consider the scenario where we have these two components: export class HeroComponent { constructor( public service1: Service1, public service2: Service2, ) { // perform some action } } export class AdvancedHeroComponent extends HeroCompone ...

What are the steps to implement the `serialport` library in `deno`?

After tinkering with Deno to extract readings from an Arduino, I encountered a roadblock when it came to using the serialport library correctly. Here is what I attempted: According to a post, packages from pika.dev should work. However, when trying to use ...

Generate a list of items in typescript, and then import them into a react component dynamically

I have a variable that stores key/value pairs of names and components in a TypeScript file. // icons.tsx import { BirdIcon, CatIcon } from 'components/Icons'; interface IconMap { [key: string]: string | undefined; } export const Icons: IconM ...

Issue with RouterLink functionality in Angular 6

While following a Brad Traversy tutorial on coding, I implemented the instructions exactly as given. Below is my 'app.module.ts' file. import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/c ...

An error was encountered at line 7800, character 18 in the three-core.d.ts file in the node_modules/@types/three directory. The error message reads: "Cannot find name 'VRDisplay

I encountered an error in my Angular 6 app while running ng serve ERROR in node_modules/@types/three/three-core.d.ts(7800,18): error TS2304: Cannot find name 'VRDisplay'. node_modules/@types/three/three-core.d.ts(7801,23): error TS2304: Canno ...

Errors in TypeScript are being brought up by using if-else statements inside a loop

I am currently working on a function to retrieve referral codes from users. The user inputs a code, which is then checked against the database to see if it exists or not If the code provided matches the current user's code, it should not be accept ...

I am experiencing a 404 error when attempting to import a local JS file in Angular

After creating a new project with "ng new xxx", all you need to do is add one line of code in index.html: <!doctype html> <html lang="en> <head> <meta charset="utf-8> <title>Bbb</title> <base href="/&g ...

Error: Attempting to access the value property of a null object within a React Form is not possible

I am currently developing a form that includes an HTML input field allowing only numbers or letters to be entered. The abbreviated version of my state interface is outlined below: interface State { location: string; startDate: Date; } To initiali ...

I'm encountering an issue where the 'switchMap' property is not recognized on the 'Observable<User>' type

I encountered an issue while attempting to run code in git bash. The problem arises from 'switchMap' not being executed and displaying the error message: "error TS2339: Property 'switchMap' does not exist on type 'Observable'" ...

Is there a way to run the mediapipe face detection codepen.io demo on my laptop?

After successfully running the mediapipe face detection demo from Mediapipe official website, I wanted to replicate it on my laptop. To achieve this, I created an 'index.html' file and meticulously transferred the code from the CodePen demo page ...

Cannot assign Angular 4 RequestOptions object to post method parameter

I'm having trouble with these codes. Initially, I created a header using the code block below: headers.append("Authorization", btoa(username + ":" + password)); var requestOptions = new RequestOptions({ headers: headers }); However, when I tried to ...

Error encountered in React TypeScript: Expected symbol '>' was not found during parsing

While transitioning from JavaScript to TypeScript, I encountered an error in my modified code: Error on Line 26:8: Parsing error: '>' expected import React from "react"; import { Route, Redirect, RouteProps } from "react-router ...

Issue with mediaelement in Angular 8: video playback does not display after 2 seconds

I'm encountering an issue with MediaElement js when trying to play videos in .m3u8 format within a slider containing multiple videos. Whenever I click on the play button for any video, only a 2-second clip is shown before the video disappears. Any as ...

Dealing with code in Angular when transitioning to a different component

I have an Angular component that displays data and includes a button called "Go to Dashboard". I want to implement a feature where the user can either click on this button to navigate to the dashboard or have the application automatically redirect them aft ...

Convert your socket.io syntax to TypeScript by using the `import` statement instead

const io = require('socket.io')(server, { cors: { origin: '*', } }); Is there a way to convert this code to TypeScript using the syntax import {} from ''; ...

Tips for selecting specific types from a list using generic types in TypeScript

Can anyone assist me in creating a function that retrieves all instances of a specified type from a list of candidates, each of which is derived from a shared parent class? For example, I attempted the following code: class A { p ...

Differences between `typings install` and `@types` installation

Currently, I am in the process of learning how to integrate Angular into an MVC web server. For guidance, I am referring to this tutorial: After some research and noticing a warning from npm, I learned that typings install is no longer used. Instead, it ...

Exploring ways to retrieve checkbox values instead of boolean values upon submission in Angular

I am currently working with a template-driven form and facing an issue. Despite receiving true or false values, I actually need to retrieve the value of checkboxes. <form #f = "ngForm" (ngSubmit) = "onClickSubmit(f.value)"> ...