Angular E2E testing with mocked httpBackend? - angularjs

A hot discussion raised between me and my boss about Angular E2E testing. according to vojitajina pull request we need to run a server in order to run the e2e tests. So, running e2e test involves a real server, a real server involves DB. This makes test slow.Ok, now the question is how to test e2e without involving a real server ? is there a way to use httpBackend to and the e2e angular API where I can use browser(), element(), select(), for my tests ?

[see edit below] We use a shell script to periodically capture curl requests from our seeded test server. These responses are then returned via $httpBackend.whenGet(...).respond() to intercept and return that data.
So, in our index.html
if (document.location.hash === '#test') {
addScript('/test/lib/angular-mocks.js');
addScript('/test/e2e/ourTest.js');
window.fixtures = {};
addScript('/test/fixtures/tasks/tasks_p1.js');
// the tasks_p1.js is generated and has "window.fixtures.tasks_p1 = ...json..."
addScript('/test/fixtures/tasks/tasks_p2.js');
// the tasks_p2.js is generated and has "window.fixtures.tasks_p2 = ...json..."
addScript('/test/e2e/tasks/taskMocks.js\'><\/script>');
}
ourTest.js
angular.module('ourTestApp', ['ngMockE2E','taskMocks']).
run(function ($httpBackend, taskMocks) {
$httpBackend.whenGET(/views\/.*/).passThrough();
$httpBackend.whenGET(/\/fixtures.*\//).passThrough();
taskMocks.register();
$httpBackend.whenGET(/.*/).passThrough();
});
taskMocks.js
/*global angular */
angular.module('taskMocks', ['ngMockE2E']).
factory('taskMocks', ['$httpBackend', function($httpBackend) {
'use strict';
return {
register: function() {
$httpBackend.whenGET(..regex_for_url_for_page1..).respond(window.fixtures.tasks_p1);
$httpBackend.whenGET(..regex_for_url_for_page2..).respond(window.fixtures.tasks_p2);
}
};
}]);
In a few cases our data is related to the current date; e.g. "tasks for this week", so we massage the captured data in the register() method.
EDIT
We now use Rosie to create factories for our mocked objects. So, no more CURLing test server for expected json responses. index.html no longer loads these ".js" fixtures and the mocks look like:
taskMocks.js
/*global angular */
angular.module('taskMocks', ['ngMockE2E']).
factory('taskMocks', ['$httpBackend', function($httpBackend) {
'use strict';
var tasks_p1 = [ Factory.build('task'), Factory.build('task')],
tasks_p2 = [ Factory.build('task'), Factory.build('task')]
return {
register: function() {
$httpBackend.whenGET(..regex_for_url_for_page1..).respond(tasks_p1);
$httpBackend.whenGET(..regex_for_url_for_page2..).respond(tasks_p2);
}
};
}]);

To answer your question, yes there is a way to do it without involving a real server and you can use $httpBackend to mock responses in e2e tests, but it is not as straightforward as with mocking them in the unit tests. You also need to include angular-mocks.js in the index.html as well as 'ngMockE2E' in app.js.
How to mock an AJAX request?

Related

Jasmine JSON fixtures VS service mocking

Assume we have a service which calls api and we use this service to do some logic in a controller.
What is better to use?
user = $injector.get('userSrv');
var myFixture = angular.fromJson(window.__html__['mydata.json']);
$httpBackend.whenGET('url/').respond(myFixture);
user.getGender();
or just using
beforeEach(module(function($provide) {
$provide.service('userSrv', function(){
return {
getGender: function(){
return 'something';
}
}
});
})
Both should be used, but in different tests.
In controller spec, a service is supposed to be mocked, because the unit under test is a controller.
In service spec, http request is supposed to be mocked, because the unit under test is a service (this allows to keep the test synchronous and independent from the backend, it is not possible to perform real requests with ngMock any way).
This allows to unambiguously determine which unit failed when a test becomes red.

How to avoid code duplication in AngularJS Jasmine tests

I'm part of a team that's been working on angularjs for quite a while now and our unit tests consist of files which contain both the test logic and provider mocks of each component that tested element relies on.
This is leading to more and more code duplication, can anyone recommend an angular-ey method whereby we could maintain a test infrastructure, mock a controller or service once and be able to inject that mocked service into other tests as required?
Edit The code I'm working with is proprietary but for an example consider you have 10 or 15 services which retrieve data via HTTP requests, format the data in different ways and return that data. One example might return arrays of data so in each file which relies on the service we would have initialising code like the following ( cut down and altered so please ignore any typos or other mistakes )
myDataService: {
getDataParsedLikeY: {
[{obj1},{obj2}...]
},
getDataParsedLikeX: {
[{obja},{objb}...]
}
beforeEach(angular.mock.module('myModule'));
beforeEach(angular.mock.inject(function(myDataService) {
myDataService = function( functionName ) {
return myDataService[functionName];
}
spyOn(myDataService).and.callThrough();
})
}
If you are looking for a way not to declare same mock codes in every test files, I would suggest something like below although I'm not sure this is angular-way or not.
[1] write the code to declare your mock services like below only for unit test and import after angular-mocks.js
In your-common-mocks.js(you can name the file as you like)
(function(window){
window.mymock = {};
window.mymock.prepare_mocks = function(){
module(function($provide) {
$provide.service('myDataService', function(){
this.getDataParsedLikeY = function(){
return 'your mock value';
};
});
});
};
})(window);
If you use karma, you could write like this to import the file in karma.conf.js.
files: [
...
'(somedirctory)/angular-mocks.js',
'(somedirctory)/your-common-mocks.js'
...
]
[2] use mymock.prepare_mocks function in your test files.
describe('Some Test', function() {
beforeEach(mymock.prepare_mocks);
it('getDataParsedLikeY', inject(function(myDataService){
expect('your mock value', myDataService.getDataParsedLikeY());
As a result of above, you have to write your mock code just one time and share the mock code in your every test files. I hope this could suggest you something to achieve what you want.
If your jasmine version is 2.x, you can write test code like below without writing mock service.
spyOn(YourService, 'somemethod').and.returnValue('mock value');
In jasmine 1.3 you need to adjust little bit.
spyOn(YourService, 'somemethod').andReturn('mock value');
I hope this could help you.

Angular Karma Unit Test: How to inject a mock service in karma configuration instead of in all test specs

Recently, I have added a security feature to an existing angular app. Here is what I got afterwards:
Chrome 3X.0.2125 (Linux) ERROR
Some of your tests did a full page reload!
Chrome 3X.0.2125 (Linux): Executed 23 of 102 (skipped 2) ERROR
This is how I have set up the security feature:
angular.module('myapp', [/*..I have omitted..*/])
.run(function(MyLoginSerivce, /*.. I have omitted ..*/)){
if(!MyLoginService.isLoggedIn()){
MyLoginService.redirectForLogin();
}else{
/* other logics */
}
}
I knew I have to add the following code to each and every test spec. But it sounds silly adding it to dozens of test files.
beforeEach(module(function($provide){
$provide.value("MyLoginServce", {
isLoggedIn: function(){
return true;
},
redirectForLogin: function {}
});
}));
Is there a way to tell Karma that use a mock service and add that piece of code only once and in one place?
Thanks
Current solution
Step 1: I saved this in a separate file, e.g. ./test/mocked.login.service.js:
var mockedLoginService = {
isLoggedIn: function(){
return true;
},
redirectForLogin: function {}
});
Step 2: I include the file in karma.conf.js, by inserting 'test/mocked.login.service.js'
Step 3: I just use it in my tests like the following:
beforeEach(module(function($provide){
$provide.value("MyLoginServce", mockedLoginService
}));
You can extract the mocked service into a separate js file as an object, include that file in the karma.conf files list, then use that object as a global in your specs.

How do I provide re-usable sample data values to my angularjs / jasmine unit-tests

I would like to provide simple constant values such as names, emails, etc to use in my jasmine unit tests.
A similar question was asked here: AngularJS + Karma: reuse a mock service when unit testing directives or controllers
In c# I would create a static class to hold little snippets of mock data. I can then use these values throughout my unit tests, like this:
static class SampleData
{
public const string Guid = "0e3ae555-9fc7-4b89-9ea4-a8b63097c50a";
public const string Password = "3Qr}b7_N$yZ6";
public const string InvalidGuid = "[invalid-guid]";
public const string InvalidPassword = "[invalid-password]";
}
I would like to have the same convenience when testing my AngularJS app using Karma / Jasmine.
I know that I can define a constant object against my angular app, I already do this for constants I use in the real code, like this:
myApp.constant('config', {apiUrl:'http://localhost:8082'})
I could add another constant just like this but only containing sample data values for use in my unit tests, like this:
myApp.constant('sampleData', {email: 'sample#email.com'})
I could then just inject the mock constant object into my tests and off I go, like this
describe 'A sample unit test', ->
beforeEach -> module 'myApp'
beforeEach inject ($injector) ->
#sampleData = $injector.get 'sampleData'
email = #sampleData.email
# etc ...
However this seems a bit fishy to me. I don't want my production code to contain sample data that is only required by my unit-tests.
How would you conveniently provide your angular / jasmine unit tests with re-usable sample data values?
Thanks
There are two ways of doing this:
spy on function calls and return fake values.
create mock classes (and possibly mock data to initialise them) and load them wherever you need
The first one is alright when you only have to fake a few calls. doing that for a whole class is unsustainable.
For example, let's say you have a service that builds some special URLs. If one of the methods depends on absUrl, you can fake it by spying on the method in the $location object:
describe('example') function () {
beforeEach(inject(function () {
spyOn($location, 'absUrl').andCallFake(function (p) {
return 'http://localhost/app/index.html#/chickenurl';
});
}));
it('should return the url http://www.chicken.org') ... {
// starting situation
// run the function
// asserts
}
Now let's say that you have a Settings Service that encapsulates data like language, theme, special paths, background color, typeface... that is initialised using a remote call to a server.
Testing services that depend on Settings will be painful. You have to mock a big component with spyOn every time. If you have 10 services... you don't want to copypaste the spyOn functions in all of them.
ServiceA uses Settings service:
describe('ServiceA', function () {
var settings, serviceA;
beforeEach(module('myapp.mocks.settings')); // mock settings
beforeEach(module('myapp.services.serviceA')); // load the service being tested
beforeEach(inject(function (_serviceA_, _settings_) {
serviceA = _serviceA_;
settings = _settings_;
}));
container
for this test suite, all calls to the Settings service will be handled by the mock, which has the same interface as the real one, but returns dummy values.
Notice that you can load this service anywhere.
(If, by any reason, you needed to use the real implementation, you can load the real implementation before the mock and use spyOn for that particular case to delegate the call to the real implementation.)
Normally you'll place the mocks module outside of the app folder. I have a test folder with the unit tests, e2e tests and a lib folder with the angular-mocks.js file. I place my mocks there too.
Tell karma the files you need for the tests:
files: [
'app/lib/jquery/jquery-1.9.1.js',
'test/lib/jasmine-jquery.js',
'app/lib/angular/angular.js',
'app/lib/angular/angular-*.js',
'test/lib/angular/angular-mocks.js',
'test/lib/myapp/*.js', /* this is mine */
'app/js/**/*.js',
'test/unit/**/*.js'
],
The file tests/lib/myapp.mocks.settings.js looks just like any other module:
(function () {
"use strict";
var m = angular.module('myapp.mocks.settings', []);
m.service('settings', function () { ... })
})
Second problem (optional): you want to change quickly the dummy values. In the example, the settings service fetches an object from the server when it is instantiated for the first time. then, the service has getters for all fields. This is kind of a proxy to the server: instead of sending a request everytime you need a value, fetch a bunch of them and save them locally. In my application, settings don't change in the server in run-time.
Something like:
1. fetch http://example.org/api/settings
2. var localSettings = fetchedSettings;
3 getFieldA: function() { return localSettings.fieldA; }
Go and pollute the global namespace. I created a file settings.data.js with a content like:
var SETTINGS_RESPONSE = { fieldA: 42 };
the mock service uses this global variable in the factory to instantiate localSettings

Loading a mock JSON file within Karma+AngularJS test

I have an AngularJS app set up with tests using Karma+Jasmine. I have a function I want to test that takes a large JSON object, converts it to a format that's more consumable by the rest of the app, and returns that converted object. That's it.
For my tests, I'd like you have separate JSON files (*.json) with mock JSON content only--no script. For the test, I'd like to be able to load the JSON file in and pump the object into the function I'm testing.
I know I can embed the JSON within a mock factory as described here: http://dailyjs.com/2013/05/16/angularjs-5/ but I really want the JSON to not be contained within script--just straight JSON files.
I've tried a few things but I'm fairly noob in this area. First, I set up my Karma to include my JSON file just to see what it would do:
files = [
...
'mock-data/**/*.json'
...
]
This resulted in:
Chrome 27.0 (Mac) ERROR
Uncaught SyntaxError: Unexpected token :
at /Users/aaron/p4workspace4/depot/sitecatalyst/branches/anomaly_detection/client/anomaly-detection/mock-data/two-metrics-with-anomalies.json:2
So then I changed it to just serve the files and not "include" them:
files = [
...
{ pattern: 'mock-data/**/*.json', included: false }
...
]
Now that they're only served, I thought I'd try to load in the file using $http from within my spec:
$http('mock-data/two-metrics-with-anomalies.json')
When I ran the spec I received:
Error: Unexpected request: GET mock-data/two-metrics-with-anomalies.json
Which in my understanding means it expects a mocked response from $httpBackend. So...at this point I didn't know how to load the file using Angular utilities so I thought I'd try jQuery to see if I could at least get that to work:
$.getJSON('mock-data/two-metrics-with-anomalies.json').done(function(data) {
console.log(data);
}).fail(function(response) {
console.log(response);
});
This results in:
Chrome 27.0 (Mac) LOG: { readyState: 4,
responseText: 'NOT FOUND',
status: 404,
statusText: 'Not Found' }
I inspect this request in Charles and it's making a request to
/mock-data/two-metrics-with-anomalies.json
Whereas the rest of the files I've configured to be "included" by Karma are being requested at, for example:
/base/src/app.js
Apparently Karma's setting up some sort of base directory to serve the files from. So for kicks I changed my jquery data request to
$.getJSON('base/mock-data/two-metrics-with-anomalies.json')...
And it works! But now I feel dirty and need to take a shower. Help me feel clean again.
I'm using an angular setup with angular seed. I ended up solving this with straight .json fixture files and jasmine-jquery.js. Others had alluded to this answer, but it took me a while to get all the pieces in the right place. I hope this helps someone else.
I have my json files in a folder /test/mock and my webapp is in /app.
my karma.conf.js has these entries (among others):
basePath: '../',
files: [
...
'test/vendor/jasmine-jquery.js',
'test/unit/**/*.js',
// fixtures
{pattern: 'test/mock/*.json', watched: true, served: true, included: false}
],
then my test file has:
describe('JobsCtrl', function(){
var $httpBackend, createController, scope;
beforeEach(inject(function ($injector, $rootScope, $controller) {
$httpBackend = $injector.get('$httpBackend');
jasmine.getJSONFixtures().fixturesPath='base/test/mock';
$httpBackend.whenGET('http://blahblahurl/resultset/').respond(
getJSONFixture('test_resultset_list.json')
);
scope = $rootScope.$new();
$controller('JobsCtrl', {'$scope': scope});
}));
it('should have some resultsets', function() {
$httpBackend.flush();
expect(scope.result_sets.length).toBe(59);
});
});
The real trick was the jasmine.getJSONFixtures().fixturesPath='base/test/mock';
I had originally set it to just test/mock but it needed the base in there.
Without the base, I got errors like this:
Error: JSONFixture could not be loaded: /test/mock/test_resultset_list.json (status: error, message: undefined)
at /Users/camd/gitspace/treeherder-ui/webapp/test/vendor/jasmine-jquery.js:295
Serving JSON via the fixture is the easiest but because of our setup we couldn't do that easily so I wrote an alternative helper function:
Repository
Install
$ bower install karma-read-json --save
OR
$ npm install karma-read-json --save-dev
OR
$ yarn add karma-read-json --dev
Usage
Put karma-read-json.js in your Karma files. Example:
files = [
...
'bower_components/karma-read-json/karma-read-json.js',
...
]
Make sure your JSON is being served by Karma. Example:
files = [
...
{pattern: 'json/**/*.json', included: false},
...
]
Use the readJSON function in your tests. Example:
var valid_respond = readJSON('json/foobar.json');
$httpBackend.whenGET(/.*/).respond(valid_respond);
I've been struggling to find a solution to loading external data into my testcases.
The above link: http://dailyjs.com/2013/05/16/angularjs-5/
Worked for me.
Some notes:
"defaultJSON" needs to be used as the key in your mock data file, this is fine, as you can just refer to defaultJSON.
mockedDashboardJSON.js:
'use strict'
angular.module('mockedDashboardJSON',[])
.value('defaultJSON',{
fakeData1:{'really':'fake2'},
fakeData2:{'history':'faked'}
});
Then in your test file:
beforeEach(module('yourApp','mockedDashboardJSON'));
var YourControlNameCtrl, scope, $httpBackend, mockedDashboardJSON;
beforeEach(function(_$httpBackend_,defaultJSON){
$httpBackend.when('GET','yourAPI/call/here').respond(defaultJSON.fakeData1);
//Your controller setup
....
});
it('should test my fake stuff',function(){
$httpBackend.flush();
//your test expectation stuff here
....
}
looks like your solution is the right one but there are 2 things i don't like about it:
it uses jasmine
it requires new learning curve
i just ran into this problem and had to resolve it quickly as i had no time left for the deadline, and i did the following
my json resource was huge, and i couldn't copy paste it into the test so i had to keep it a separate file - but i decided to keep it as javascript rather than json, and then i simply did:
var someUniqueName = ... the json ...
and i included this into karma conf includes..
i can still mock a backend http response if needed with it.
$httpBackend.whenGET('/some/path').respond(someUniqueName);
i could also write a new angular module to be included here and then change the json resource to be something like
angular.module('hugeJsonResource', []).constant('SomeUniqueName', ... the json ... );
and then simply inject SomeUniqueName into the test, which looks cleaner.
perhaps even wrap it in a service
angular.module('allTestResources',[]).service('AllTestResources', function AllTestResources( SomeUniqueName , SomeOtherUniqueName, ... ){
this.resource1 = SomeUniqueName;
this.resource2 = SomeOtherUniqueName;
})
this solutions was faster to me, just as clean, and did not require any new learning curve. so i prefer this one.
I was looking for the same thing. I'm going to try this approach. It uses the config files to include the mock data files, but the files are a little more than json, because the json needs to be passed to angular.module('MockDataModule').value and then your unit tests can also load multiple modules and then the value set is available to be injected into the beforeEach inject call.
Also found another approach that looks promising for xhr requests that aren't costly, it's a great post that describes midway testing, which if I understand right lets your controller/service actually retrieve data like in an e2e test, but your midway test has actual access to the controller scope (e2e doesn't I think).
There are Karma preprocessors that work with JSON files also. There is one here:
https://www.npmjs.org/package/karma-ng-json2js-preprocessor
And shameless plug, this is one I developed that has RequireJS support
https://www.npmjs.org/package/karma-ng-json2js-preprocessor-requirejs
You can use the karma-html2js-preprocessor to get the json files added to the __html__ global.
see this answer for details: https://stackoverflow.com/a/22103160/439021
Here is an alternative to Cameron's answer, without the need for jasmine-jquery nor any extra Karma config, to test e.g. an Angular service using $resource :
angular.module('myApp').factory('MyService', function ($resource) {
var Service = $resource('/:user/resultset');
return {
getResultSet: function (user) {
return Service.get({user: user}).$promise;
}
};
});
And the corresponding unit test :
describe('MyServiceTest', function(){
var $httpBackend, MyService, testResultSet, otherTestData ;
beforeEach(function (done) {
module('myApp');
inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
MyService = $injector.get('MyService');
});
// Loading fixtures
$.when(
$.getJSON('base/test/mock/test_resultset.json', function (data) { testResultSet = data; }),
$.getJSON('base/test/mock/test_other_data.json', function (data) { otherTestData = data; })
).then(done);
});
it('should have some resultset', function() {
$httpBackend.expectGET('/blahblahurl/resultset').respond(testResultSet);
MyService.getResultSet('blahblahurl').then(function (resultSet) {
expect(resultSet.length).toBe(59);
});
$httpBackend.flush();
});
});

Resources