Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save jamescookie/f7b854226ca1e133d1b7b2798095b7f4 to your computer and use it in GitHub Desktop.
Save jamescookie/f7b854226ca1e133d1b7b2798095b7f4 to your computer and use it in GitHub Desktop.

Mocking HttpClient calls in Angular 2 when using Protractor

I just started writing an app using Spring Boot/Angular 2 combo. Once I had the initial demo up and working and cracked wiring in Spring security (another blog entirely), my attention turned to writing some tests that would drive the next phases.

My Spring Boot back-end can be unit/integration tested on it's own. I was interested in making sure the user experience was tested. Tradionally I might have turned to Geb for a full server side project, but this is mostly just javascript and I know how fast Jasmine/Karma tests run, so there must be a nice way of doing this?

Indeed I was not disappointed to find that Angular ng new webapp had already created me a e2e folder with a skeleton Protractor test to point the way.

But how to stub out the back-end? I have used many mocking frameworks before; Sinon, for example, is excellent in unit tests. There must by a simple way of doing it for Protractor/Angular.

However, I was astonished to find this issue that has been open for over 2 years suggesting that there is no mechanism!

So I thought I would write this post about how I overcame this problem using an old favourite of mine re-written for javascript: Wiremock.

Configurable base URL

With the Spring Boot back-end my URLs are all relative as the app is all hosted together, so first I needed a way to call out to wiremock instead.

So I created a interceptor: src/main/webapp/src/app/interceptors/base-url.interceptor.ts

import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import {Injectable} from "@angular/core";
import {environment} from '../../environments/environment';

@Injectable()
export class BaseUrlInterceptor implements HttpInterceptor {
    constructor() {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const apiReq = request.clone({url: `${environment.baseUrl}${request.url}`});
        return next.handle(apiReq);
    }
}

Don't forgot to include it in your providers!

    providers: [
...    
        { provide: HTTP_INTERCEPTORS, useClass: BaseUrlInterceptor, multi: true }
    ],

And gave prod evironment (src/main/webapp/src/environments/environment.prod.ts) an empty base url:

export const environment = {
    production: true,
    baseUrl: ''
};

And my non-prod environment (src/main/webapp/src/environments/environment.ts) the wiremock url:

export const environment = {
    production: false,
    baseUrl: 'http://localhost:9999'
};

Setting up wiremock

After installing wiremock (npm install jswiremock --save-dev), you can then create a wrapper for it (not really necessary, but it makes the tests cleaner) src/main/webapp/e2e/wiremock.ts

let jswiremocklib = require('jswiremock');

export class Wiremock {
    jswiremock;

    constructor() {
        this.jswiremock = new jswiremocklib.jswiremock(9999); //note the port here matches the port in the baseUrl
    }

    stop() {
        this.jswiremock.stopJSWireMock();
    }

    get(url: string, body: object) {
        jswiremocklib.stubFor(this.jswiremock, jswiremocklib.get(jswiremocklib.urlEqualTo(url))
            .willReturn(jswiremocklib.a_response()
                .withStatus(200)
                .withHeader({"Content-Type": "application/json"})
                .withBody(JSON.stringify(body))));
    }
}

Using it in tests

import {AppPage} from './app.po';
import {Wiremock} from "./wiremock";

describe('App', () => {
    let page: AppPage;
    let wiremock: Wiremock;

    beforeEach(() => {
        page = new AppPage();
        wiremock = new Wiremock();
    });

    afterEach(() => {
        wiremock.stop();
    });

    it('should login', () => {
        page.navigateTo();
        page.login().click().then(function () {
            wiremock.get("/api/user", {name: "fred"});
        });
        page.sendCredintials("user", "password");
        expect(page.logout().isPresent()).toBeTruthy();
    });
});

The only tricky part here is stubbing the call at the correct time, if you do it too early, you may find your test is already logged in before it even starts.

Hope it helps!

@drajapandian
Copy link

drajapandian commented Apr 23, 2019

@gaeljw I am trying the same above, my api calls are not getting mocked, also not getting any error. Did you face any problem while implementing the above and if you have fixed any, can you please share it here?

@gaeljw
Copy link

gaeljw commented Apr 23, 2019

@drajapandian It was working fine without any fix from what I remember. But we didn't kept it for long as it was not powerful enough. We prefer to use Cypress for E2E tests now, or unit tests with the HttpClientTestingModule.

@drajapandian
Copy link

Thanks for your reply....So in Cypress , can we do mocking the api? If yes can you please tell the npm module...

@gaeljw
Copy link

gaeljw commented Apr 23, 2019

Yes you can with Cypress but it's probably a very different approach of how you write your E2E tests today. It's not just a library, it's a complete framework for tests.
See https://www.cypress.io/blog/2017/12/11/an-alternative-to-protractor-for-angular-projects/# or https://medium.com/@ronnieschaniel/getting-started-with-cypress-e2e-testing-in-angular-bc42186d913d.
There is plenty of articles explaining how to set it up.

@drajapandian
Copy link

Thanks a lot !... I will check this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment