Override $exceptionHandler behaviour in service unit test - angularjs

I have a service which throws an error, and I'd like to unit test that, but somehow I cannot get it overwritten, which I did manage to do for controllers:
describe('some test', function () {
var myService, exceptionHandler;
beforeEach(angular.mock.module('myModule', function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode("log");
}));
beforeEach(inject(function ($injector, $exceptionHandler) {
exceptionHandler = $exceptionHandler;
myService = $injector.get('myService');
}));
it('should throw an error', function () {
theService.somefunction('create an error');
expect(exceptionHandler.errors.length).toBe(1);
expect(exceptionHandler.errors[0].message).toBe('some text');
});
});
});
in somefunction I just do this:
throw new Error('some text')
The problem is that the error is just logged to the console in Karma, so it seems to be rethrown rather then logged.
How can I fix this?

It's advised to use sinon when stubbing an object (will show both ways though):
describe('some test', function () {
var myService, exceptionHandler = {};
exceptionHandler.fn = function(exception, cause) {
throw exception;
};
beforeEach(module('myModule', function($provide) {
$provide.factory('$exceptionHandler', function(){
return function(exception, cause){
return exceptionHandler.fn(exception, cause);
};
});
}));
beforeEach(inject(function ($injector) {
myService = $injector.get('myService');
}));
it('should throw an error', function () {
var old = exceptionHandler.fn, caught = false, message = '';
exceptionHandler.fn = function(exception, cause) {
caught = true;
message = exception.message;
};
myService.somefunction('create an error');
expect(caught).toBe(true);
expect(message).toBe('message');
exceptionHandler.fn = old; //restore
// using sinon.js
sinon.stub(exceptionHandler, 'fn', function(exception){ });
myService.somefunction('create an error');
expect(exceptionHandler.fn.called).toBe(true);
expect(exceptionHandler.fn.getCall(0).args[0]).toMatch(/message/);
exceptionHandler.fn.restore();
});
});
sinon makes it easier to stub stuff, and check the call count and restore functionality after you're done

Related

Testing then and catch from a promise in angular

I would like to test my then and catch function from my $scope.customerinfo. The problem is i dont know how exactly.
var app = angular.module('shop', ['ngRoute','ngResource'])
.factory('Customerservice', function ($resource) {
return $resource('http://localhost:8080/Shop/:customer',{customer: "#customer"});
})
.controller('customerController', function ($scope,Customerservice) {
$scope.customerinfo = CustomerService.get({customer: "Mark"});
$scope.customerinfo.$promise.then(function(info) {
return info;
}).catch(function(errorResponse) {
throw errorResponse;
});
});
Im not done yet but this is my jasmine code
describe('Testing the customerinfo', function () {
var $scope;
var $q;
var deferred;
beforeEach(module('shop'));
beforeEach(inject(function($controller, _$rootScope_, _$q_) {
$q = _$q_;
$scope = _$rootScope_.$new();
deferred = _$q_.defer();
$controller('userController', {
$scope: $scope
});
}));
it('should reject promise', function () {
// I want to check if the catch option is working
});
});
So how exactly can i do this, or do i need to refactor the code?
The jasmine 'it' method takes a done parameter that you can call for async testing
it('Should reject', function(done) {
someAsyncFunction().catch(function(result) {
expect(result.status).toBe(401);
done();
});
});

Testing q.defer() with a webworker, how do I prevent timing issues?

I have a webworker doing some work for me.
I've wrapped this into a service and this webworker is executed in a Promise.
Now I'm tesing this with Jasmine, and it seems that the promised is returned after the test has finished.
The difficulty in here is that the defer AND the webworker are both async at different points in time.
I've tried with async jasmine with done, setTimeout, $scope.$apply(). But ''deferred.resolve(e.data.filtered)'' is called after all those timers have suspended.
My angular service is like this:
'use strict';
angular.module('app.demographics').service('FilteringService', FilteringService);
FilteringService.$inject = ['$q'];
function FilteringService($q) {
this.filter = function (dataSet, filters) {
var deferred = $q.defer();
var worker = new Worker('my.worker.js');
var filterData = {
dataSet: dataSet,
filters: filters
};
worker.postMessage(filterData);
worker.onmessage = function (e) {
if (e.data && e.data.ready) {
deferred.resolve(e.data.filtered);
}
};
worker.onerror = function (e) {
console.log("something went wrong while filtering: ", e);
deferred.reject(e);
};
return deferred.promise;
};
}
And my test is like this, which I expect to work properly, but it never comes to the expect.
'use strict';
describe('FilteringService: ', function () {
var filteringService, $q,
dataSet = [{a: 1, b: 2}, {c: 3, d: 4}],
filters = [];
beforeEach(function () {
module('app.demographics');
inject(function (_$rootScope_, _FilteringService_, _$q_) {
filteringService = _FilteringService_;
$q = _$q_;
});
});
it('should return a promise on filtering', function () {
var filteringPromise = filteringService.filter(dataSet, filters);
filteringPromise.then(function (data) {
expect(data.length).toEqual(dataSet.length);
}, function (failure) {
fail(failure);
});
});
});
As mentioned in https://stackoverflow.com/a/37853075/1319998, the original test seems to be more of an integration test rather than a unit test. If you would like this to be a unit test....
You need to be able to mock the worker so you're not testing what it does. So in the service, instead of calling Worker directly, you can call $window.Worker, since $window can be easily mocked in tests.
app.service('FilteringService', FilteringService);
FilteringService.$inject = ['$window', '$q', '$rootScope'];
function FilteringService($window, $q, $rootScope) {
this.filter = function (dataSet, filters) {
var deferred = $q.defer();
var worker = new $window.Worker('my.worker.js');
...
Then in the test you can create a mocked worker, calling the attacted onmessage handler that would be called by the real worker, and testing that the promise then gets resolved with the correct value (I've left it as just testing the length, but in a real test I suspect you will need something a bit better).
describe('FilteringService: ', function () {
var $rootScope, filteringService, $q,
dataSet = [{a: 1, b: 2}, {c: 3, d: 4}],
filters = [];
var mockWorker;
var mockWindow = {
Worker: function() {
return mockWorker;
}
};
beforeEach(function () {
module('app.demographics');
module(function($provide) {
$provide.value('$window', mockWindow);
});
inject(function (_$rootScope_, _FilteringService_, _$q_) {
$rootScope = _$rootScope_;
filteringService = _FilteringService_;
$q = _$q_;
});
mockWorker = {
postMessage: jasmine.createSpy('onMessage')
}
});
it('when onmessage from worker called, resolves returned promise with filtered list', function () {
expect(mockWorker.postMessage).not.toHaveBeenCalled();
expect(mockWorker.onmessage).not.toEqual(jasmine.any(Function));
var filteringPromise = filteringService.filter(dataSet, filters);
expect(mockWorker.postMessage).toHaveBeenCalled();
expect(mockWorker.onmessage).toEqual(jasmine.any(Function));
mockWorker.onmessage({
data: {
ready: true,
filtered: dataSet
}
});
var result;
filteringPromise.then(function(_result) {
result = _result;
});
$rootScope.$apply();
expect(result.length).toEqual(dataSet.length);
});
});
Note you then need the $apply in the test (but not the service), to make sure the promise callbacks get called.
You can see this working at https://plnkr.co/edit/g2q3ZnD8AGZCkgkkEkdj?p=preview
I accept this isn't the optimal solution, more of a hack probably, but this is how I got Jasmine working with Angular. My approach was to create a function digestIt that takes the done function provided by Jasmine and invokes $digest using setInterval and returns a cleanup function.
function digestIt($rootScope, done) {
var intervalId: number,
_done = function() {
if (angular.isDefined(intervalId))
clearInterval(intervalId);
intervalId = null;
done();
},
_interval = function () {
if (angular.isNumber(intervalId)) {
try {
$rootScope.$digest();
} catch (e) {
_done();
}
}
},
intervalId = setInterval(_interval, 1);
return _done;
}
Here's the usage pattern.
describe("MyService ", function() {
var $rootScope,
$injector
;
beforeEach(angular.mock.inject(function (_$rootScope_, _$injector_) {
$rootScope = _$rootScope_.$new();
$injector = _$injector_;
}));
it("My Test", function (done) {
var $docs = $injector.get('MyService'),
completed = digestIt($rootScope, done)
;
$docs.asyncCall().then(function () {
/* expect */
}).catch(function() {
/* fail */
}).finally(function () {
completed();
});
});
});
It looks likes (at least in the testing environment), $q promises only get resolved (as in, their success/failure callbacks are called) when a digest cycle gets initiated. So in the service you can put in $rootScope.apply() to trigger this:
worker.onmessage = function (e) {
if (e.data && e.data.ready) {
$rootScope.$apply(function() {
deferred.resolve(e.data.filtered);
});
}
};
worker.onerror = function (e) {
console.log("something went wrong while filtering: ", e);
$rootScope.$apply(function() {
deferred.reject(e);
});
};
And then your test can be asynchronous:
it('should return a promise on filtering', function (done) {
var filteringPromise = filteringService.filter(dataSet, filters);
filteringPromise.then(function (data) {
expect(data.length).toEqual(dataSet.length);
done();
}, function (failure) {
fail(failure);
});
});
This can be seen at https://plnkr.co/edit/D21EhoCXIbj8R0P9RY40?p=preview
Note: this is probably classified as an integration test rather than a unit test, as you're testing both FilteringService and your worker together. If you were to only have a unit test, you can probably avoid the addition of $rootScope.$apply() in FilteringService by mocking the worker. You would probably also then be able to make is a synchronous test.

Integration Testing AngularJS + Karma + Jasmine

I would like to test my angular service I would like to test it with real data - a.k.a (Integration Test). I'm using Jasmine and Karma.
Here is my test:
describe('Trending Data Service', function () {
var value = 0, originalTimeout = 0;
var service, Enums, $httpBackend;
// initialize module
beforeEach(module('waterfall'));
// initialize services
beforeEach(inject(function ($injector) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
$httpBackend = $injector.get('$httpBackend');
service = $injector.get("trendingService");
Enums = $injector.get("Enums");
spyOn(service, 'fetch').and.callThrough();
}));
it('check if dependencies are defined', function () {
expect(service).toBeDefined();
expect(Enums).toBeDefined();
expect(service.categories).toBeDefined();
expect(service.fetch).toBeDefined();
});
it('categories array should be defined within the service', function () {
expect(service.categories.length).toEqual(9);
expect(service.categories).toEqual(jasmine.any(Array));
});
// this test is alway fails...
it('fetch method should return initial result', function (done) {
var promise = service.fetch(Enums.socials.viewAll, false);
promise.then(function (result) {
done();
}, function() {
expect(1).toBe(2);
done.fail('Error occured');
});
});
}
This is the error:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I tried a wide range of solutions and I haven't got any success with this.
EDIT: 29/April/2016
var trendingService = function ($q, $http) {
var deferred = $q.defer();
var $this = this;
this.fetch = function (id) {
$http.get(url).then(function (result) {
deferred.resolve(result);
}).catch(function(err) {
deferred.reject(err);
});
return deferred.promise;
}
return {
fetch: $this.fetch;
};
};
var Enums = {
Roles: {
Admin: 1,
User: 2,
NotRegistered: 0
}
};
angular.module('').const('Enums', Enums);
Karma isn't meant for integration testing. Your call to module('waterfall') is actually a reference to angular.mock.module which mocks all $https calls.
You need to use some form of end-to-end testing to test with real data. I suggest http://angular.github.io/protractor/#/.

Mocking $mdSideNav in unit test

I have a simple enough function that closes an $mdSidenav instance in my application
function closeSideNav() {
$mdSidenav('left').close();
}
I'm now needing to unit test this, but am having trouble writing an expectation for the close() call on $mdSidenav.
I thought about using $provide in my test spec
module(function($provide) {
$provide.value('$mdSidenav', function(id) {
return {
close: jasmine.createSpy('$mdSidenav.close')
}
})
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
spyOn($mdSidenav, 'close');
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect($mdSidenav.close).toHaveBeenCalled();
});
});
This throws a couple of errors:
Error: close() method does not exist
Error: Expected a spy, but got undefined.
Has anyone managed to mock out $mdSidenav and offer me some guidance please?
Thanks
UPDATE
Based on the suggested answer, I have now updated my test spec to
'use strict';
describe('NavbarController', function() {
var $controller,
vm,
$mdSidenav,
sideNavCloseMock;
beforeEach(function() {
module('app.layout');
sideNavCloseMock = jasmine.createSpy();
module(function($provide) {
$provide.value('$mdSidenav', function() {
return function(sideNavId) {
return {close: sideNavCloseMock}
}
})
});
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
});
});
And for a sanity check, my actual controller looks as follows:
(function () {
'use strict';
angular
.module('app.layout')
.controller('NavbarController', Controller);
Controller.$inject = ['$mdSidenav'];
function Controller($mdSidenav) {
var vm = this;
vm.closeSideNav = closeSideNav;
//This only affects the sideNav when its not locked into position, so only on small\medium screens
function closeSideNav() {
$mdSidenav('left').close();
}
}
})();
Unfortunately this still isn't working for me, and I end up with a different error
TypeError: undefined is not a constructor (evaluating '$mdSidenav('left').close())
close method doesn't belong to $mdSidenav. $mdSidenav is a function that returns a side nav object. That's why it complains 'close() method does not exist'.
What you can do is mock the $mdSidenav to return an object hat has mocked close method, like this: -
var sideNavCloseMock;
beforeEach(module(function($provide){
sideNavCloseMock = jasmine.createSpy();
$provide.factory('$mdSidenav', function() {
return function(sideNavId){
return {close: sideNavCloseMock};
};
});
}));
then do
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});

How to write test case for JSON getting form factory in AngularJS

I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});

Resources