mock $httpBackend in angular e2e tests - angularjs

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?

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.

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.

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);
});
});

Related

Injector returns undefined for $httpBackend

I've been looking into unit testing a project I'm doing for Angular (my first). I've set up a DataTree service that depends on a NodeFactory and also makes $http calls.
First off, (a snippet of) my test:
describe("the data tree service", function() {
let DataTree, NodeFactory, $httpBackend;
beforeEach(module('memApp.common'));
beforeEach( inject(function(_DataTree_, _NodeFactory_, _$httpBackend_) {
DataTree = _DataTree_;
NodeFactory = _NodeFactory_;
$httpBackend = _$httpBackend_;
}));
describe("is sane, so it", function() {
$httpBackend
.when('GET', 'json/home.json')
.respond(200, {
"esv": {
"_title" : "ESV",
"_filePath" : "json/bookData.json"
}
});
...
}
...
}
It is trying to set up a mock backend responding with JSON.
My Karma tests fail on the $httpBackend.when, specifically:
TypeError: Cannot read property 'when' of undefined
at <the httpBackend.when call>
My DataTree service (a service service - just want one global instance of it):
(function() {
angular.module('memApp.common')
.service('DataTree', ['NodeFactory', '$http', '$q',
function(NodeFactory, $http, $q) {
...
}]);
})();
My NodeFactory factory - a wrapper for a class:
(function() {
angular.module('memApp.common')
.factory('NodeFactory', function() {
....
});
})();
Finally, my included karma.conf.js files:
files: [
'bower_components/angular/angular.min.js',
'bower_components/angular-mocks/angular-mocks.js',
'bower_components/angular-route/angular-route.js',
'js/common/common.module.js', //memApp.common module def'd here
'js/common/node.service.js', //NodeFactory def'd here
'js/common/datatree.service.js', //DataTree def'd here
'js/common/common.controller.js',
'js/hierarchy/hierarchy.module.js',
'js/hierarchy/hierarchy.controller.js',
'js/rehearsal/rehearsal.module.js',
'js/rehearsal/rehearsal.controller.js',
'js/app/app.module.js',
'js/hierarchy/hierarchy.directive.js',
'js/rehearsal/rehearsal.directive.js',
'spec/*Spec.js' //all Jasmine tests here - in particular, there is a test suite for NodeFactory here.
],
I don't think this is relevant, but NodeFactory's test does the following (and I have been burned by what other tests are doing before...):
describe("the Node service", function() {
let NodeFactory;
let home;
beforeEach(module('memApp.common'));
beforeEach( inject(function(_NodeFactory_) {
NodeFactory = _NodeFactory_;
home = NodeFactory.create();
}));
...
});
I have gotten tests in my data tree service to pass without doing anything with $httpBackend. This sorta worries me as I think my service shouldn't be making actual $http calls in a unit test?
I am a bit of a newbie to ng-mock (and am taking the PluralSight course on said topic) but I'm trying to follow along with my project and this is blocking me from doing much. In general, $httpBackend still feels a tad magical.
Well, heh...writing out my question sometimes goes a long way in answering it. (I spent a day looking at this, I swear.)
My call to $httpBackend wasn't contained in a test (just a describe block), so the beforeEach wasn't being called before it. I moved the code into an it...test, and the injection now works.

AngularJS testing with Jasmine

I'm a newbie to programming and I'm trying to figure out how to unit test angularJS code with jasmine, and its driving me insane!
This is the angular code im trying to test, its all set up on an asp.net web application using abpBoilerplate and angular. The result of the code below is that when a button is clicked on the web page, a 'success' popup appears and "true" appears in a text box, if the service is available. The service is being pulled from classes within a web api project.
(function() {
var controllerId = 'app.views.home';
angular.module('app').controller(controllerId, [
'$scope', 'abp.services.lfcservice.webapi', function($scope,lfcServices) {
var vm = this;
//Home logic...
vm.CheckLfcIsAvailable = function () {
lfcServices.lfcIsAvailable()
.success(function () {
abp.notify.info('Success');
vm.Available = 'True';
});
};
I just need to know how to write a jasmine test that passes when it expects a true value for the lfc service. Ive tried loads of different combinations with no success, I could paste in 10 different attempts ive had in here but they are all very different.
Any help would be much appreciated!
First, you need to know how to test a controller, mocking the service.
Then, you need to mock the service API to return a promise.
let's say thet the controller is initiated with Available = false;.
Test an angular 1.x controller (see jsFiddle):
describe("app.views.home controller spec", function() {
var ctrl;
//depend on the module
beforeEach(module('app'));
beforeEach(inject(function($controller) {
//use angular's "$controller" to get the controller
ctrl = $controller("app.views.home");
}));
it("available should be false", function() {
expect(ctrl.Available).toBe(false);
});
});
Now, let's asume that the service returns a simple result (without promises) and see how do we provide a mock service instead of the real service.
Test an angular 1.x controller with mock service (see jsFiddle):
beforeEach(module(function($provide) {
var mockService = jasmine.createSpyObj('mock', ['lfcIsAvailable']);
mockService.lfcIsAvailable.and.returnValue(true);
$provide.value('abp.services.lfcservice.webapi', mockService);
}));
Now, let's see how to mock a promise response. for this we will use $q.
Mock angular 1.x promise (see jsFiddle):
it('should change after promise resolved', inject(function($q, $rootScope) {
//create promise
var deferred = $q.defer();
//mock service response
mockService.lfcIsAvailable.and.returnValue(deferred.promise);
//call CheckLfcIsAvailable ()
ctrl.CheckLfcIsAvailable ();
expect(ctrl.Available).toBe(false);
deferred.resolve(true);
//not yet...
expect(ctrl.Available).toBeNull(false);
//from angular $q documentation:
//"it's important to know that the resolution of promises is tied to the digest cycle"
$rootScope.$apply();
//now!
expect(ctrl.Available).toBe(true);
}));

Unit testing a factory with angularjs and acute reaching the API

I want to unit test a $resource but actually reach the REST API and get it's respone. So, I don't want to mock it.
I am very new to unit testing and here is what I did:
describe('Factory: PartnersResource', function () {
var PartnersResource,
$httpBackend;
beforeEach(module('app'));
beforeEach(inject(function (_PartnersResource_, _$httpBackend_) {
PartnersResource = _PartnersResource_;
$httpBackend = _$httpBackend_;
}));
it('when post, it should query all partners and return them', function () {
var result = PartnersResource.query();
$httpBackend.flush();
});
});
It does not work obviously :)
Error: Unexpected request: GET https://www-dev.mysite.com/film/partner
No more request expected
The resource works, normally but it won't in this unit test.
I use ngMocks to simulate a fake back-end for modules of the app I am working, ahead of the API.
I have setul the mocks to allow calls to my url:
$httpBackend.whenGET(/www-dev/).passThrough();
Can anyone help?

How to mock service in angularAMD with karma/jasmine?

I have a project using AngularAMD/RequireJS/Karma/Jasmine, that I have the basic configuration all working, most unit tests run and pass successfully.
I cannot get a mocked service injected correctly using either angular.mock.module or angularAMD.value().
I have:
// service definition in services/MyService.js
define(['app'],
function(app) {
app.factory('myService', [ '$document', function($document) {
function add(html) {
$document.find('body').append(html);
}
return { add: add };
}]);
}
);
// test
define(['angularAMD', 'angular-mocks', 'app', 'services/MyService'],
function(aamd, mocks, app) {
describe('MyService', function() {
var myBodyMock = {
append: function() {}
};
var myDocumentMock = {
find: function(sel) {
// this never gets called
console.log('selector: ' + sel);
return myBodyMock;
}
};
var svc;
beforeEach(function() {
// try standard way to mock a service through ng-mock
mocks.module(function($provide) {
$provide.value('$document', myDocumentMock);
});
// hedge my bets - try overriding in aamd as well as ng-mock
aamd.value('$document', myDocumentMock);
});
beforeEach(function() {
aamd.inject(['myService',
function(myService) {
svc = myService;
}]);
});
it('should work', function() {
// use svc expecting it to have injected mock of $document.
spyOn(myDocumentMock, 'find').andCallThrough();
spyOn(myBodyMock, 'append');
svc.add('<p></p>');
expect(myDocumentMock.find).toHaveBeenCalledWith('body');
expect(myBockMock.append).toHaveBeenCalledWith('<p></p>');
});
});
}
);
Does anyone know where I'm going wrong ? Any help would be much appreciated.
Angular isn't asynchronous, I think is not a good ideia use both. If you're trying to reach to a good modularization method, okay, but use the RequireJS optimizer to build everything before you put this on your browser, and about the tests, I think you can just use RequireJS optimizer to build your modules before, it will let you free from "CommonJS environment even in tests".
Looks like it'll be an issue with variable scopes, karma is very finicky about that. I think you should initialize your mock objects globally, then set them in the beforeEach.
The top line of my test files always looks something like:
var bodyMock, svcMock, foo, bar
Then in the beforeEach'es I set the values
Edit: Since bodyMock is only a scope variable, at the point where the tests are actually running and the browser is looking for an object 'bodyMock', it can't find anything.

Is it possible to perform e2e test with Karma and angular with a real httpBackend?

I am in the development phase of a small web project and I would like to write some end to end tests that exercise my rest services, and ensure they behave as expected. I think this project would get more out of testing the full flow than it would testing individual components.
I am using karma for the remainder of my angular client tests, and would like to keep to one test runner.
I know this isn't going to work, without some other magic:
describe('e2e: http factory test Definition', function(){
'use strict';
var myHttp = null ;
beforeEach(module('myNgApplication'));
beforeEach(inject(function (MyHttp) {
myHttp = MyHttp
}))
it('has a functioning MyHttp factory object', (function (done) {
//test the get
var getPromise = myHttp.
path('/').
path('rest').
path('v1').
path('is-alive').
get()
getPromise.then(function(data){
expect(data).to.be.a('string')
var getDataObject = JSON.parse(data)
done()
}) ;
}));
}); //completed test case for case-summary

Resources