How to switch between httpBackendMocks in protract

2020-03-24 06:05发布

I am trying to mock responses to API calls within Protractor tests. In different tests (and within tests), the application will POST to an API (always the same URL) with different data POSTed, and expecting different responses.

Specifically, it is a search engine, and I am sending different queries and expecting different results back. I have it working successfully like the code below, but it is getting unmanageable:

var httpBackendMock = function() {
  angular.module('httpBackendMock', ['ngMockE2E'])
    .run(function($httpBackend) {
      $httpBackend.whenPOST('//search_endpoint').respond(function(method, url, query, headers) {
        query = JSON.parse(query);
        if (query.bla = 'foo') {
          var results = {... lots of json ...};
        } else if (query.bla = 'bar') {
          var results = {... lots of json ...};
        } else if (query.something.else != 'whatever') {
          var results = {... lots of json ...};
        ... etc ...
        } else {
          var results = {... lots of json ...};
        }
        return [200, results];
      });
      $httpBackend.whenGET(/.*/).passThrough();
    })
};

beforeEach(function(){
  browser.addMockModule('httpBackendMock', httpBackendMock);
});

What I'd like to do is have each possible response in a separate mock, then remove the beforeEach and add the mocks when needed, like so:

it('paginates', function(){
  // mocking a search with 13 results, showing 10 results per page
  browser.addMockModule('search_results', <some function>);
  $('#searchbox').sendKeys('some keyword search');
  $('#searchbutton').click();
  expect($('#results li').count()).toEqual(10);
  browser.clearMockModules();
  browser.addMockModule('search_results_page2', <some other function>);
  $('#next').click();
  expect($('#results li').count()).toEqual(3)
});

There are two problems with this.

1) It doesn't work. After clearing and adding the second mock, getRegisteredMockModules() shows only the second mock, however it seems like the first mock is still getting used, based on the expects and manual inspection when using browser.pause() with ChromeDriver. It seems that you can't change the mocks without at least reloading the page.

2) Even if it did work, there is a huge amount of repeated code for each mock module, as it has to set up everything, including the passThrough().

What would be even better would be if I could pass my desired response into the mock I am adding, however I tried that and anything passed into my own function isn't available within the angular.module scope. The only way I could think of to do that would be to create another angular module with a provider that had a single variable that would keep track of which response was desired, and inject that into the mocked module. I haven't tried that yet but it seems like an unnecessarily complex solution.

1条回答
虎瘦雄心在
2楼-- · 2020-03-24 06:25

A mock-module in protractor is basically code that gets executed into the browser upon each full-page-refresh. It's a mechanism to save you the hassle of doing that yourself, since such a refresh cleans the state of the browser completely (with the exception of cookies of course). As you already found out, until you trigger such a refresh (with browser.get()), your modules are never executed. You can do that manually with browser.executeScript() if you want.

Regarding the mess that ensues from mocking your back-end - I took the following approach: Have a default mock implementation for your back-end while making it easily overridable and registering it before each test with an init function:

mocked-backend-base.js

exports.httpBackendMockBase = function () {
    var exposeBackendCalls = function ($httpBackend) {
        this.getLoginAuthenticated = $httpBackend.whenGET(/login\/authenticated.*/);
        this.getFindStuff = $httpBackend.whenGET(/lookup\/findStuff.*/);
        this.getFullProfile = $httpBackend.whenGET(/api\/full.*/);
    };

    angular.module('httpBackendMockBase', ['myClientApp', 'ngMockE2E'])
    .service('httpBackendMockBase', exposeBackendCalls)
    .run(function (httpBackendMockBase, testFixture) {
        httpBackendMockBase.getLoginAuthenticated.respond(function () {
            return [200, null, {}];
        });
        httpBackendMockBase.getFindStuff.respond(function () {
            return [200, { stuff: testFixture.stuff }, {}];
        });
        httpBackendMockBase.getFullProfile.respond(function () {
            return [200, { profile: testFixture.fullProfile }, {}];
        });
    });
};

If you need parts of it overridden at some point register a new mock module. Remove it in your afterEach block:

mocked-backend-special-user.js

exports.httpBackendMock = function() {
    angular.module('httpBackendMockSpecialUser', []).run(function (httpBackendMockBase, testFixture) {
        httpBackendMockBase.getLoginAuthenticated.respond(function() {
            return [200, testFixture.specialUser, {}];
        });
    });
};

The testFixture is from another module that holds our data and is registered before the mocked-backend-base:

fixture.js

exports.data = {
    stuff: null,
    fullProfile: {},
    specialUser: {}
};

exports.module = function (data) {
    angular.module('backendFixture', []).constant('testFixture', data);
};

The init function:

var fixtureModule = require('fixture');
var baseMockedBackend = require('mocked-backend-base');

browser.addMockModule('backendFixture', fixtureModule.module, fixtureModule.data);
browser.addMockModule('httpBackendMockBase', baseMockedBackend.httpBackendMockBase);
查看更多
登录 后发表回答