mock $httpBackend in angular e2e tests
Asked Answered
B

3

8

Does anyone have an idea how to mock $httpBackend in angular e2e tests? The idea is stubbing XHR requests while running tests on travis-ci. I'm using karma to proxy assets and partials from my rails app running on travis. I want to do acceptance testing without real DB queries.

Here is part of my karma config file:

...
files = [
  MOCHA,
  MOCHA_ADAPTER,

  'spec/javascripts/support/angular-scenario.js',
  ANGULAR_SCENARIO_ADAPTER,

  'spec/javascripts/support/angular-mocks.js',
  'spec/javascripts/e2e/**/*_spec.*'
];
...

proxies = {
  '/app': 'http://localhost:3000/',
  '/assets': 'http://localhost:3000/assets/'
};
...

Here is part of my spec file:

beforeEach(inject(function($injector){
  browser().navigateTo('/app');
}));

it('should do smth', inject(function($rootScope, $injector){
  input('<model name>').enter('smth');
  //this is the point where I want to stub real http query
  pause();
}));

I have tried to receive $httpBackend service through $injector:

$injector.get('$httpBackend')

But this is not the one that is used inside iframe where my tests run.

The next try I made was using angular.scenario.dsl, here is code samle:

angular.scenario.dsl('mockHttpGet', function(){
  return function(path, fakeResponse){
    return this.addFutureAction("Mocking response", function($window, $document, done) {
      // I have access to window and document instances 
      // from iframe where my tests run here
      var $httpBackend =  $document.injector().get(['$httpBackend']);
      $httpBackend.expectGET(path).respond(fakeResponse)
      done(null);
    });
  };
});

Usage example:

it('should do smth', inject(function($rootScope, $injector){
  mockHttpGet('<path>', { /* fake data */ });
  input('search.name').enter('mow');
  pause();
}));

This leads to following error:

<$httpBackend listing>  has no method 'expectGET'

So, at this point I have no idea of next step. Have anyone tried doing something like this, is this type of stubbing really possible?

Basinet answered 29/5, 2013 at 6:40 Comment(1)
how do you configure your karma to have "inject" function in your spec? I kept getting ReferenceError for my testsCathepsin
W
7

If you are really trying to mock out the backend in a E2E test (these tests are called Scenarios, while Specs are used for unit testing) then this is what I did in a project I was working on earlier.

The application I was testing was called studentsApp. It was an application to search for students by querying a REST api. I wanted to test the application without actually querying that api.

I created a E2E application called studentsAppDev that I inject studentsApp and ngMockE2E into. There I define what calls the mockBackend should expect and what data to return. The following is an example of my studentsAppDev file:

"use strict";

// This application is to mock out the backend. 
var studentsAppDev = angular.module('studentsAppDev', ['studentsApp', 'ngMockE2E']);
studentsAppDev.run(function ($httpBackend) {

    // Allow all calls not to the API to pass through normally
    $httpBackend.whenGET('students/index.html').passThrough();

    var baseApiUrl = 'http://localhost:19357/api/v1/';
    var axelStudent = {
        Education: [{...}],
        Person: {...}
    };
    var femaleStudent = {
        Education: [{...}],
        Person: {...}
    };
    $httpBackend.whenGET(baseApiUrl + 'students/?searchString=axe&')
        .respond([axelStudent, femaleStudent]);
    $httpBackend.whenGET(baseApiUrl + 'students/?searchString=axel&')    
        .respond([axelStudent, femaleStudent]);
    $httpBackend.whenGET(baseApiUrl + 'students/?searchString=axe&department=1&')
        .respond([axelStudent]);
    $httpBackend.whenGET(baseApiUrl + 'students/?searchString=axe&department=2&')
        .respond([femaleStudent]);
    $httpBackend.whenGET(baseApiUrl + 'students/?searchString=axe&department=3&')    
        .respond([]);

    ...

    $httpBackend.whenGET(baseApiUrl + 'departments/?teachingOnly=true')
        .respond([...]);
    $httpBackend.whenGET(baseApiUrl + 'majors?organization=RU').respond([...]);
});

Then, I have a first step in my Jenkins CI server to replace the studentsApp with studentsAppDev and add a reference to angular-mocks.js in the main index.html file.

Wilow answered 24/6, 2013 at 21:42 Comment(4)
No need to setup & create a separate app just for mocking $httpBackend. I described how to set it up so that it can be used for both unit & E2E testing altogether here: blogs.burnsidedigital.com/2013/09/….Philina
Oh that's a good solution Dida, thanks for that write up. It's definitely something I'll look into for the next time I'll be having to mock out the backend.Dwaynedweck
@Dida Link above is not working now. Can you provide a short explanation of what you did? Maybe as a new answer. Thanks.Shay
In the meantime, you can use the archived version from archive.org located at: web.archive.org/web/20140713175521/http://…Dwaynedweck
C
1

Mocking out your backend is an important step in building a complex Angular application. It allows testing to be done without access to the backend, you don't test things twice and there are less dependencies to worry about.

Angular Multimocks is a simple way to test how your app behaves with different responses from an API.

It allows you to define sets of mock API responses for different scenarios as JSON files.

It also allows you to change scenarios easily. It does this by allowing you to compose “scenarios” out of different mock files.

How to add it to your app

After adding the required files into your page, simply add scenario as a dependency to your application:

angular
  .module('yourAppNameHere', ['scenario'])
  // Your existing code here...

Once you have added this to your app you can start to create mocks for API calls.

Lets say your app makes the following API call:

$http.get('/games').then(function (response) {
  $scope.games = response.data.games;
});

You can create a default mock file:

Example of someGames.json

{
  "httpMethod": "GET",
  "statusCode": 200,
  "uri": "/games",
  "response": {
    "games": [{"name": "Legend of Zelda"}]
  }
}

When you load your application, calls to /games will return 200 and {"games": [{"name": "Legend of Zelda"}]}

Now lets say you want to return a different response for the same API call, you can place the application in a different scenario by changing the URL e.g. ?scenario=no-games

The no-games scenario can use a different mock file, lets say one like this:

Example of noGames.json

{
  "httpMethod": "GET",
  "statusCode": 200,
  "uri": "/games",
  "response": {
    "games": []
  }
}

Now when you load your application, calls to /games will return 200 and {"games": []}

Scenarios are composed of various JSON mocks in a manifest like this:

{
  "_default": [
    "games/someGames.json"
  ],
  "no-games": [
    "games/noGames.json"
  ]
}

You can then exclude the mock files and strip the scenario dependency in your production app.

Chairman answered 25/1, 2016 at 22:40 Comment(0)
M
0

This feels more like unit/spec testing. Generally speaking you should use mocks within unit/spec tests rather than e2e/integration tests. Basically, think of e2e tests as asserting expectations on a mostly integrated app...mocking out things kind of defeats the purpose of e2e testing. In fact, I'm not sure how karam would insert angular-mocks.js into the running app.

The spec test could look something like...

describe('Controller: MainCtrl', function () {
    'use strict';

    beforeEach(module('App.main-ctrl'));

    var MainCtrl,
        scope,
        $httpBackend;

    beforeEach(inject(function ($controller, $rootScope, $injector) {
        $httpBackend = $injector.get('$httpBackend');
        $httpBackend.when('GET', '/search/mow').respond([
            {}
        ]);
        scope = $rootScope.$new();
        MainCtrl = $controller('MainCtrl', {
            $scope: scope
        });
    }));

    afterEach(function () {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    it('should search for mow', function () {
        scope.search = 'mow';
        $httpBackend.flush();
        expect(scope.accounts.length).toBe(1);
    });
});
Madelenemadelin answered 20/6, 2013 at 20:49 Comment(1)
Code is not related to question - question is not about unit tests and code is for Jasmine unit tests. inject is not defined in e2e tests.Spithead

© 2022 - 2024 — McMap. All rights reserved.