Test an Angular service by expecting a function call: Jasmine createSpy - javascript

I have a controller that calls a service function, by passing in 2 functions as parameters (to be called dependent on the outcome of the service function), like so:
Controller code:
onSuccess(data){ ... }
onError(data){ ... }
service.post(url, query, onSuccess, onError);
Service code:
var service = function($log, $http, $location, $interval) {
...
var post = function(url, query, onSuccess, onError){
$http.post(url, query)
.success(function(data, status, headers, config){
onSuccess(data);
})
.error(function(data, status, headers, config){
onError(data);
});
};
...
});
What I'm trying to do is test that the query passed into the post function is correct and so the onSuccess function located within the controller code is called. I've tried to test this like so:
Test code:
describe("post", inject(function (service) {
beforeEach(module('myApp'));
beforeEach(function() {
var url = "http://..."
var query = {
...
}
var mockSuccess = jasmine.createSpy('Success function');
var mockError = jasmine.createSpy('Error function');
service.post(url, query, mockSuccess, mockError);
}
it('Should call success function following call to http.post', function () {
expect(mockSuccess).toHaveBeenCalled();
});
))};
Although I'm getting the error:
TypeError: null is not an object (evaluating 'currentSpec.$modules')
So I'm not sure how to properly test whether my query is successful in returning data through the http request?
Thanks in advance.

Related

AngularJS - Best approach for taking data from factory and if not there use http service

In my angularJS application, I have set data received from one controller's ajax and put in factory method to reuse in other controllers. The problem is when user reload page, this factory method is useless.
app.factory('factory', function () {
return {
set: set,
get: get
}
});
app.controller ('testController', function ($scope, factory) {
if (factory.get('name')) {
$scope.name= factory.get('name');
} else {
$http.get(url).then(function(res) {
$scope.name= res.name;
factory.set('name', res.name);
});
}
});
What is the best way to retrieve this check in factory method which will get value from factory, if not there take from http service and in controller common code needs handle these two cases that done factory method?
Here when data taken from http service it returns promise otherwise plain value.
This code will fetch data from server if the 'cachedResult' variable is not set, otherwise return a promise from the stored variable.
app.factory('factoryName', ['$http','$q', function($http,$q) {
var cahedResult = null;
var myMethod = function(args) {
var deferred = $q.defer();
if (cahedResult){
deferred.resolve({ data : cahedResult });
return deferred.promise;
}
$http.get(....).then(function(response){
cahedResult = response.data;
deferred.resolve({ data : cahedResult });
},function(error){
deferred.reject('error');
});
}
};
return {
myMethod : myMethod
}
])
But you could also store the data in local storage or in a cookie when the first controller fetch the info so that it's available even if user refresh the browser.
First create a factory :
app.factory('factoryName', ['$http','$q', function($http,$q) {
return {
getData : function(arg1,arg2) {
return $http.get('/api-url'+arg1+'/'+arg2)
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
return $q.reject(response.data);
}
}, function(response) {
return $q.reject(response.data);
});
}
}
}])
Provide every api details in this factory, so you can use this factory in multiple controllers.
In Controller inject the factory (factoryName) as a dependency.
app.controller ('testController',['$scope', 'factoryName', function($scope, factoryName) {
factoryName.getData(arg1,arg2,...)
.then(function(data) {
console.log(data);
}, function(error){
console.log(error);
});
}]);
For detailed description :- http://sauceio.com/index.php/2014/07/angularjs-data-models-http-vs-resource-vs-restangular/
Directly return a promise from your factory
app.factory('factoryName', function () {
var connectionurl ='...'; //Sample: http://localhost:59526/Services.svc
return {
connectionurl : connectionurl ,
methodName: function (parameters) {
$http({
method: 'GET',
url: connectionurl + 'servicemethod_name'
})}
}
}
});
In this case your controller will look like
app.controller ('testController', function ($scope, factoryName) {
factoryName.methodName(parameters).then(function(){
$scope.variable=response.data;
/*binding your result to scope object*/
}, function() {
/*what happens when error occurs**/
});
});
Other way is directly bind to a scope object through success callback function
app.factory('factoryName', function () {
var connectionurl ='...'; //Sample: http://localhost:59526/Services.svc
return {
connectionurl : connectionurl ,
methodName: function (parameters,successcallback) {
$http({
method: 'GET',
url: connectionurl + 'servicemethod_name'
}).success(function (data, status, headers, config) {
successcallback(data);
})
.error(function (data, status, headers, config) {
$log.warn(data, status, headers, config);
});
}
}
});
In this case your controller will look like
app.controller ('testController', function ($scope, factoryName) {
factoryName.methodName(parameters,function(response){
/* your success actions*/
$scope.variable=response;
});
});
In the second case your error is handled in the factory itself. How ever you can also use errorcallback function.

Angular $scope not available outside my function

Somewhat new to Angular and javascript. I have the following controller written use a factory service to access a local JSON file. Most of this code is derived (or completely taken) from this post by Dan Wahlin. I am unable to access the $scope.books variable outside of the function and cannot figure out why. The console.log inside the function gives me the object I am looking for, but the one outside returns undefined. What am I doing wrong here? Thanks.
app.controller('FormController', ['$scope', 'tocFactory', function ($scope, tocFactory) {
$scope.books;
getBooks();
function getBooks() {
tocFactory.getBooks().
success(function(data, status, headers, config) {
$scope.books = data;
console.log($scope.books);
}).
error(function(data, status, headers, config) {
// log error
})
}
console.log($scope.books);
}]);
Because you are making an ajax and then executing the next line of code. Which doesn't mean you have guarantee that your ajax response is ready on execution of next line of code.
You could always gets its response value inside the success function of $http which gets called when ajax call executed successfully.
Read here How asynchronous call works?
actually this isn't an issue of scope, but timing. The getBooks function is executed asynchronously, so when your console log happens it most likely will not have been bound to anything. You could test this easily with interval to see how this is happening:
app.controller('FormController', ['$scope', 'tocFactory', function($scope, tocFactory) {
$scope.books;
getBooks();
function getBooks() {
tocFactory.getBooks()
.success(function(data, status, headers, config) {
$scope.books = data;
console.log($scope.books);
})
.error(function(data, status, headers, config) {
// log error
})
}
setInterval(function(){
console.log($scope.books);
}, 1000);
}]);
You can use $q service to handle asynchronous code with promises :
app.controller('FormController', ['$scope', '$q', 'tocFactory', function ($scope, $q, tocFactory)
{
var getBooks = function()
{
var deferred = $q.defer();
tocFactory.getBooks().
success( function(data, status, headers, config)
{
$scope.books = data;
deferred.resolve();
} ).
error( function(data, status, headers, config)
{
deferred.reject();
} );
return deferred.promise;
};
getBooks().then( function(res)
{
console.log($scope.books); // success : your data
}, function(res)
{
console.log($scope.books); // error : undefined
} );
console.log($scope.books); // undefined
} ] );
I haven't tested this code but it should work and show you promises principle.
More about $q service : https://docs.angularjs.org/api/ng/service/$q

Angular JS $http request - caching success response

I've got a simple dataFactory that retrieves some posts:
dataFactory.getPosts = function () {
if (this.httpPostsData == null) {
this.httpPostsData = $http.get("http://localhost/matImms/wp-json/posts?type=journey&filter[posts_per_page]=-1&filter[order]=ASC&filer[orderby]=date")
.success(function (posts) {
})
.error(function (posts) {
console.log('Unable to load post data: ' + JSON.stringify(posts));
});
}
return (this.httpPostsData);
}
The controller calls the factory and I understand that the posts are promises -so there is some stuff done on success and some stuff that is done anyway. This works fine.
.controller('CardsCtrl', function($scope, dataFactory,
$ionicSlideBoxDelegate, $stateParams) {
var parentID = $stateParams.parentID;
var keyIDNumber = $stateParams.keyID;
$scope.card = [];
var httpcall = dataFactory.getPosts()
.success(function (posts) {
$scope.card = dataFactory.getChildPosts(parentID, posts, keyIDNumber);
$ionicSlideBoxDelegate.update();
});
// do other stuff ......
});
However, I'm now trying to cache the post data - but when the controller is called a second time it returns the error .success is not a function. I assume the is because the posts have already been returned - but how do I handle this?
That's because you're not returning the $http.get, you're returning the promise after .success and .error have already been handled.
You can either change the controller to call .then on the return, or change the service to just return the $http.get (remove the .success and .error) and handle them in the controller.
If you change the controller to use .then you'll also need to update the .success function in the service to return posts;.
Have you tried setting the cache option to true in your $http call? Like here https://stackoverflow.com/a/14117744/1283740
Maybe something like this...
angular.module('foo', [])
.factory('dataFactory', ['$http', function($http){
var dataFactory = {
getPosts: getPosts
};
function getPosts(){
var url = "http://localhost/matImms/wp-json/posts?type=journey&filter[posts_per_page]=-1&filter[order]=ASC&filer[orderby]=date"
return $http({ cache: true, url: url, method: 'GET'})
.error(function (posts) {
console.log('Unable to load post data: ' + JSON.stringify(posts));
});
};
return dataFactory;
}])

How to return a value from a service in angular js?

I know that the data is coming from the server (I have unit tests and have seen the data in the debugger in chrome) but I can't figure out how to return the data from the angular service to the angular controller.
Service:
UPDATED
surchargeIndex.service('customerService', [
'$http', function ($http) {
this.getTest = function () {
return $http({
method: "GET",
url: "api/Customer/GetTest",
})
.success(function(data) {
return data;
});
};
}
]);
Controller:
surchargeIndex.controller('SurchargeIndexController', function ($scope, customerService, templateService) {
$scope.customers = customerService.getTest();
});
Data has the array from the server so the array is populated in the service. So to reiterate the data is there; however, I receive a 404 error INSIDE of the success handler during debugging.
What am I missing?
$http works asynchronously; fortunately it returns a promise which will be fulfilled when the response retrieved from the server. So you should return $http's get method and use returned promise to handle data.
this.getTest = function () {
return $http({
method: "GET",
url: "api/Customer/GetTest",
})
.success(function(data) {
return data;
})
.error(function() {
alert("failed");
}); // This returns a promise
};
Then in your controller you should use that promise to retrieve the expected data.
surchargeIndex.controller('SurchargeIndexController', function ($scope, customerService, templateService) {
//Use the returned promise to handle data, first parameter of the promise is used for successful result, if error happens, second parameter of the promise returns the error and you can do your error handling in that function
customerService.getTest().then(function(customers){$scope.customers = customers;}, function(err){console.error(err);})
});
You need to define a Callback to get your data "back" to your controller, after an async http call... There are different ways to do... I will show you one way without a callback, or a promise, but the best way would be to use a callback, or promise...
Wild West Way:
app.controller('myCTRL', function($scope, myService) {
$scope.valueWanted = myService.valueWanted;
myService.getData();
});
app.service('myService', function($http) {
var myThis = this;
this.valueWanted = "";
this.getData = function () {
$http.get('api/Customer/GetTest').success(function (data) {
myThis.valueWanted = data.valueWanted;
});
};
});

Getting JavaScript error on asynchronous postback scope function

I am getting the following error reported on angular.min.js
0x800a1391 - JavaScript runtime error: 'Error' is undefined
with the following code:
Javascipt:
function Sucess()
{
//close
}
function Save()
{
var e = document.getElementById('FormDiv');
scope = angular.element(e).scope();
scope.Apply(Sucess)
}
My Angular scope function:
function RolesCtrl($scope, $http, $location)
{
$scope.Apply = function (CallBackSucess) {
var userId = getQSP('uid', decode(document.URL));
$http({
method: 'POST', url: 'MultiRole.aspx/Apply',
data: {}
}).
success(function (data, status, headers, config) {
// this callback will be called asynchronously
CallBackSucess();
$scope.f = data.d;
}).
error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
$scope.name = 'error';
})
}
}
Everything seems to be working fine until CallBackSucess() call is made which throws the error:
0x800a1391 - JavaScript runtime error: 'Error' is undefined
The CallBackSucess argument is passed to the .Apply() method. It is not passed to the .success() method - they are chained methods with separate scopes. Thus, in the .success() callback function, CallbackSucess() is not defined when you try to call it and thus you get an error.
Also, do you really mean to spell Sucess incorrectly?
FYI, I had to format your code like this in order to see what was actually going on:
function RolesCtrl($scope, $http, $location) {
$scope.Apply = function (CallBackSucess) {
var userId = getQSP('uid', decode(document.URL));
$http({
method: 'POST',
url: 'MultiRole.aspx/Apply',
data: {}
}).success(function (data, status, headers, config) {
// this callback will be called asynchronously
CallBackSucess();
$scope.f = data.d;
}).error(function (data, status, headers, config) {
// called asynchronously if an error occurs
// or server returns response with an error status.
$scope.name = 'error';
})
}
}

Categories