I am building AngularJS application and I am trying to unit test it. What I want to test is rejected scenario.
This is the service I am trying to test:
.factory('LoginService',['Login', 'consoleService', function(Login, consoleService){
return {
getActiveUser: function() {
return Login.query({}).$promise.then(
function (users) {
return users[0];
},
function (error) {
consoleService.print(error);
}
);
}
};
}]);
I am trying to write unit test for rejected scenario (error occurred). And I want to be sure that print function is gonna be called when some error occurres. I tried different examples, but neither one didnĀ“t work. Does anyone have idea what is correct way to test it?
The Mock rejection service has already been discussed in the below link
How to use mock $httpBackend to test error branch
You could mock the Login service, then depending on a variable have it call the pass or fail functions.
var failLogin;
beforeEach(module(function ($provide) {
failLogin = false;
var mockLogin = {
query: function(data) {
return {
$promise: {
then: function(pass, fail) {
if (failLogin) {
fail('failure data');
} else {
pass('pass data');
}
}
}
}
}
}
$provide.value('login', mockLogin);
}
it('should pass', function(){
LoginService.getActiveUser();
// should pass
});
it('should fail', function(){
failLogin = true;
LoginService.getActiveUser();
// should fail
});
Related
I added catch to AngularJS Promise to handle the exception. it works well but existing Jasmine unit test fail because mock need to be changed accordingly.
Please find the code.
In the existing API call, I added catch and handleerror function.
function calAPI(){
GetAPIData(request)
.then(updatedata)
[catch](handleError)
[finally](dofinaltask();)
}
function handleError(){
//calling again the same API if the first request fails.
GetAPIData(request)
.then(updatedata)
[finally] (dofinaltask();)
}
Existing Mock for UNit Test
GetAPIData: function(){
return {then: function() {return { finally: function(){ } }; } };
}
I added catch on this
GetAPIData: function(){
return {then: function() {return { catch: function(){ }, finally: function(){ } }; } };
}
But Unit test gives error as given below
TypeError: undefined is not constructor (evaluating GetAPIData(request).then(updatedata)[catch](handleError)[finally](dofinaltask();)
Please help me to modify either mock or catch in alternative ways
Have tried everything I've found on the internet to make this work with no success. Trying to test a function in my service, but according to my coverage I'm never accessing it. Any help would be greatly appreciated :)
Service:
'use strict';
angular.module('Service').service('configService', function(
$rootScope, $http) {
var configObj = null;
return {
getConfig: function() {
if (configObj != null) {
console.log("returning cached config");
return configObj;
}
else {
return $http.get('conf.json').then(function(res) {
$http.get(res.confLocation).then(function(
locationResponse) {
configObj = locationResponse;
$rootScope.configObj = configObj;
console.log($rootScope.configObj);
return configObj;
});
});
}
}
};
});
getConfig is never being accessed in the tests I've tried.
ServiceTests:
'use strict';
describe('Service: configService', function() {
// load the controller's module
beforeEach(module('Service'));
var configService, $httpBackend, results, tstLocation, tstRes;
var tstConfig = {
"confLocation": "local-dev-conf.json"
};
var tstConfigEmpty = {};
var tstConfigObjEmpty = {};
var tstConfigObj = {
"AWS": {
"region": "us-east-1",
"endpoint": "http://localhost:8133"
}
};
// Initialize the controller and a mock scope
beforeEach(inject(function(_configService_, _$httpBackend_) {
inject(function($rootScope) {
$rootScope.USERNAME = 'TESTER';
$rootScope.configObj = tstConfigObj;
$rootScope.locationResponse = tstLocation;
$rootScope.res = tstRes;
});
configService = _configService_;
$httpBackend = _$httpBackend_;
//Problem here??
spyOn(configService, 'getConfig').and.callFake(function() {
return {
then: function() {
return "something";
}
};
});
}));
it('should return a promise', function() {
expect(configService.getConfig().then).toBeDefined();
});
it('should test backend stuff', inject(function() {
results = configService.getConfig(tstConfig);
$httpBackend.expectGET('conf.json').respond(tstConfig);
$httpBackend.expectGET('local-dev-conf.json').respond(tstConfigObj);
$httpBackend.flush();
}));
//Thanks Miles
it('should check if it was called', inject(function() {
results = configService.getConfig().then();
expect(configService.getConfig).toHaveBeenCalled();
});
// console.log(results);
}));
it('should check for a null configObj', inject(function() {
results = configService.getConfig(tstConfigObjEmpty).then(function() {
expect(results).toBe(null);
});
// console.log(results);
// console.log(tstConfigObj);
}));
it('should check for a non-null configObj', inject(function() {
results = configService.getConfig(tstConfigObj).then(function() {
// Any string is accepted right now -- Why??
expect(results).toEqual("returning cached config");
expect(results).toBe("returning cached config");
expect(results).toBe("your mom"); // SHOULDN'T BE WORKING BUT DOES
expect(results).toEqual("Object{AWS: Object{region: 'us-east-1', endpoint: 'http://localhost:8133'}}");
expect(results).toBe("Object{AWS: Object{region: 'us-east-1', endpoint: 'http://localhost:8133'}}");
});
// console.log(results);
// console.log(tstConfigObj);
}));
it('should check for null file', inject(function() {
results = configService.getConfig(tstConfigEmpty).then(function() {
expect(results).toEqual(null);
expect(results).toBe(null);
});
}));
it('should test a valid file', inject(function() {
results = configService.getConfig(tstConfig).then(function() {
expect(results).not.toBe(null);
expect(results).toEqual("Object{confLocation: 'local-dev-conf.json'}");
})
});
I think I'm using spyOn wrong, or not accessing getConfig in my tests properly. Thoughts?
EDIT: Here is my code coverage
EDIT 2: Changed test 3 thanks to a problem found by Miles, still no update on test coverage though. Something is wrong with my spyOn logic as Amy pointed out. I shouldn't be using callFake it seems?
EDIT 3: Got it accessing the function now thanks to Miles. Had to change my spyOn to:
spyOn(configService, 'getConfig').and.callThrough();
then add the test case:
results = configService.getConfig(tstConfig).then();
expect(configService.getConfig).toHaveBeenCalled();
Coverage now (still needs work)
You're calling a fake instead of the function. So the logic inside of the function does not get called.
You have an issue here:
results = configService.getConfig(tstConfigObj).then(function() {
expect(results).toHaveBeenCalled();
expect(results).toHaveBeenCalledWith(tstConfigObj);
});
getConfig takes no parameters, and neither does then. Omitting these errors, results is assigned the string "something" from then. Even if the expect statements fire, you seem to be testing if a string has been called. Try this instead:
results = configService.getConfig().then();
expect(configService.getConfig).toHaveBeenCalled();
What version of Jasmine are you using? The and.callFake syntax was added in Jasmine 2.0. Maybe the test suite just needs to point to the new version.
Jasmine 1.3 Docs
Jasmine 2.0 Docs
I am writing a Jasmine custom matcher to use in a Protractor spec and I want to check that the browser title is equal to some string. I am unable to get this code to work properly and after spending hours debugging it, I can only assume that the browser object is not being accessed inside the matcher function as I expect it to be. When I modify the matcher function to accept browse.getTitle() as the actual argument then it works fine, which leads me to my assumption. Can anyone find the issue here and explain it to me?
beforeEach(function() {
jasmine.addMatchers({
toBeOnPage: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
var result = {};
result.pass = actual.getTitle() === expected.title;
return result;
}
};
}
});
});
var homepage = { url: 'Homepage URL', title: 'Homepage Title' };
describe('regression:', function() {
it('homepage loads successfully', function() {
browser.get('http://localhost/#/home');
expect(browser).toBeOnPage(homepage);
});
});
The problem is that getTitle() returns a promise. Resolve it:
beforeEach(function() {
jasmine.addMatchers({
toBeOnPage: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
return {
pass: actual.getTitle().then(function (title) {
return title === expected.title;
});
};
}
};
}
});
});
I have moved some common code to factory. but the controller is executing before factory get loaded. In this case i am getting the blank response(zero results)
can anyone suggest the best solution.
here is my angular factory,
app.factory('TabsFactory', function($resource){
var activetabs = {};
activetabs.getDepositAccountDetails = function() {
return $resource('xxxx/:number', {}, {
getDepositAccountDetailsService: {
method: 'GET',
isArray: false
}
});
}
activetabs.getAccountInfo = function(){
return accountinit.accountInfo;
}
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
return activetabs;
});
controller,
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo);
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
You should use chain promise to update scope variable, because your accountInfo variable is updated inside $resource promise.
Code
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo).then(function(data){
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
});
Update
Service method should return promise inorder to continue promise chain
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
//added return below
return activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
return accountinit.accountInfo;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
Yes, this will happen because of JavaScript executing asynchronous operations but your controller in such a way that it expects things to be synchronous operations.
When you call TabsFactory.getAccountInfo() its possible that your $resource('xxxx/:number') is still not completed and response ready for you to process!!
So, what to do? You have make use of promise. I usually have a repository (A factory with method that return promise) to handle server communications. Here is an example:
app.factory('accountRepository', ["$http","$q",function($http,$q){
return {
getDepositAccountDetails : function(id) {
var deferred = $q.defer();
$http.ger('xxx').success(deferred.resolve).error(deferred.reject);
return deferred.promise;
}
};
}] );
My repository will have more operations like add account, update account info etc..
my controller/service then calls these methods as follows:
accountRepository.getDepositAccountDetails(123).then(function(response) {
// Process the response..
}, function(error) {
// Some error occured! handle it
});
doing so, my code gets executed only after I get response from server and data is ready for consumption or display. Hope this helps..
Update: You might want to have a look at this to get the idea ;)
I have a factory, which I am attempting to unit test, with an injected PouchDB wrapper. The issue I am running into is that I have the mock PouchDB service returning a promise and while the promise resolves (I can console.log out the successCb and it looks correct), the karma expect statements within the successCb function where the correct console.log output is, are not running.
I am not sure why the console.log output works fine but the expect statements do not execute, any help on this matter would be greatly appreciated!
The following are the relevant snippets of code:
[ Unit Testing Setup ]
var mockCalendarsResource, mockPouchDB;
var mockPouchDB = function(name) {
this.name = name;
this.post = function(newCalendar, successCb, errorCb) {
var promise = new Promise(function(successCb, errorCb) {
newCalendar._id = 2;
successCb(newCalendar);
});
return promise;
};
}
beforeEach(function() {
module(function($provide) {
$provide.value('Pouch', mockPouchDB);
});
angular.mock.inject(function($injector) {
mockCalendarsResource = $injector.get('Calendar');
});
});
[ Karma Unit Test Code ]
describe('The calendar resource function create', function() {
it('should create a new calendar', inject(function(Calendar) {
var result = mockCalendarsResource.create({
name: "Testing"
},
function(response) {
//console.log here works correctly //
//These are the expect statements not functioning //
expect(response._id).toBe(2);
expect(response.name).toBe('Testing');
// Could put any expectation here and it will pass or not be checked //
}, function(err) {
});
}));
});
I believe you should be using Jasmine 2.0's done() to signal that the test has finished. If you don't then the async tests will not finish.