Mock Angular HttpClient in protractor e2e test

Viewed 604

My application is making an http call to my backend immediately on page load. My e2e test is failing because there is no backend running in my ci pipelines.

I have tried using the rxjs catchError piped operator on the http call

I have tried wrapping the whole http call in a try / except block

I am still getting the error showing up in the dev console (which is causing the e2e test to fail)

I am wondering how I can provide a mock HttpClient to the protractor tests?

Actual Http call

return this.http.get<any>( url ).subscribe(...)

(this.http is an instance of angular's HttpClient)

spec file:

import { AppPage } from './app.po';
import { browser, logging } from 'protractor';

describe( 'workspace-project App', () => {
  let page: AppPage;

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

  it( 'should display login page', () => {
    page.navigateTo();
    expect( page.getLoginTitleText() ).toEqual( 'Welcome to\nApp\nClick below to sign in with Google' );
  } );

  afterEach( async () => {
    // Assert that there are no errors emitted from the browser
    const logs = await browser.manage().logs().get( logging.Type.BROWSER );
    expect( logs ).not.toContain( jasmine.objectContaining( {
      level: logging.Level.SEVERE,
    } as logging.Entry ) );
  } );
} );

protractor's page object file (app.po)

import { browser, by, element } from 'protractor';

export class AppPage {
  public navigateTo() {
    return browser.get( browser.baseUrl ) as Promise<any>;
  }

  public getLoginTitleText() {
    return element( by.css( 'app-root app-login div.login-wrapper form.login section.title' ) )
      .getText() as Promise<string>;
  }
}

error in dev console: enter image description here

2 Answers

I am just responding to the question about mocking Angular HTTPClient. Assuming you have Angular version 4 or greater, there is a built in testing module for mocking httpclient called HttpClientTestingModule which you can get from the Testbed.

import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
//add these imports:
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe( 'workspace-project App', () => {
  let page: AppPage;
  var httpMock: HttpTestingController;

  beforeEach( () => {
      TestBed.configureTestingModule({
      imports: [
      HttpClientTestingModule
      ]
    });
    page = new AppPage();
    httpMock = TestBed.get(HttpTestingController);
  } );

//add async keyword to test
//mock up the get request by recreating the URL
  it( 'should display login page', async () => {
    let urlPrefix = window.location.protocol + '//' + window.location.hostname + ':' + window.location.port;
    let url = urlPrefix + 'infoo';
    let navigateTo_call = page.navigateTo();
    const request = httpMock.expectOne(url); 
    navigateTo_call.then(res => { 
   //do your checks here
   expect( page.getLoginTitleText() ).toEqual( 'Welcome to\nApp\nClick below to sign in with Google' );
  });
   request.flush("a mock response object");
    
  } );

  afterEach( async () => {
    // Assert that there are no errors emitted from the browser
    const logs = await browser.manage().logs().get( logging.Type.BROWSER );
    expect( logs ).not.toContain( jasmine.objectContaining( {
      level: logging.Level.SEVERE,
    } as logging.Entry ) );
  } );
} );

What I found personally useful is to use proxy.conf.js for it which points to assets of the frontend, but with rewritten paths.

There is a conditional response based on spec === 'user-1' in the code.

const PROXY_CONFIG = [
  {
    context: ['/api'],
    target: 'http://localhost:4200',
    secure: false,
    bypass: function (req) {
      let spec = '';
      try {
        const url = new URL(req.headers.referer);
        spec = url.searchParams.get('e2e-spec');
      } catch (e) {
        // nothing to do
      }

      if (req.method === 'GET' && req.path === '/api') {
        return '/e2e-api/index.json';
      }
      if (req.method === 'POST' && req.path === '/api/gql') {
        req.method = 'GET';
        if (spec === 'user-1') {
          return '/e2e-api/gql-user-simple.json';
        }
        return '/e2e-api/gql-empty.json';
      }
    },
    logLevel: 'debug',
  },
];

module.exports = PROXY_CONFIG;

For that we need to add an e2e configuration in angular.json:

            "e2e": {
              "fileReplacements": [
                {
                  "replace": "apps/ct-frontend/src/environments/environment.ts",
                  "with": "apps/ct-frontend/src/environments/environment.e2e.ts"
                }
              ],
              "assets": ["apps/ct-frontend/src/e2e-api"]
            }

Here is a repo with the implementation:

You can download it and run e2e tests like that:

  • npm install
  • npm run e2e ct-frontend-e2e

to check how it works with fake responses.

Related