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 expect
s 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.
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 withbrowser.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
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
The
testFixture
is from another module that holds our data and is registered before the mocked-backend-base:fixture.js
The init function: