I'm attempting to implement some http.get() requests in an angular service, returning a promise.
Here is the excerpt from my initial service:
angular.module('dashboard').service('DashboardHTTP', ['$q', '$http', function ($q, $http) {
this.get_info = function () {
var deferred = $q.defer();
$http.get('/dashboard/4/api/info', { cache: true }).success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject('Could Not Complete Request');
});
return deferred.promise;
}
}]);
And here is an excerpt from the portion of my controller where I call the service:
DashboardHTTP.get_info().then(
function (response) {
var resp = response;
$rootScope.dash_info = resp;
},
function (response) {
return 'error';
},
function (response) {
return 'notify';
});
My questions:
I'm struggling with determining how much testing is needed for an interaction like this. I currently have the following test, which is testing at the service level, but I'm wondering if I need to test at the controller level and if so what sort of testing needs to occur?
beforeEach(inject(function (_$httpBackend_, $injector) {
service = $injector.get('DashboardHTTP');
$httpBackend = _$httpBackend_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('get_info', function () {
it(' should get info from the url /api/info', function () {
var returnData = { data: 'lots of data' };
$httpBackend.expectGET('/dashboard/4/api/info').respond(returnData);
var returnedPromise = service.get_info();
var result;
returnedPromise.then(function (response) {
result = response;
});
$httpBackend.flush();
expect(result).toEqual(returnData);
});
});
My goal is that I want to set $rootScope.dash_info to the response from the HTTP request made by Service.get_info(). Is my implementation in my controller appropriate? If so, how do I test that the correct data is being passed in at the controller level?
This is probably a partial answer, but here's my input:
Your call is asynchronous, therefore your test should be. Use done.
it(' should get info from the url /api/info', function (done) {
var returnData = { data: 'lots of data' };
$httpBackend.expectGET('/dashboard/4/api/info').respond(returnData);
var returnedPromise = service.get_info();
var result;
returnedPromise.then(function (response) {
result = response;
expect(result).toEqual(returnData);
done();
});
$httpBackend.flush();
});
Also, you do know that http.get returns a promise, right? It has also success and error functions, but it is still a promise.
Related
I have this array I am getting through the following method:
var url= *url defined here*;
$scope.ViewProfile = function () {
$http.get(url)
.success(function (response) {
$scope.ProfileList = response;
$scope.FavNumbers = $scope.ProfileList[0].FavNumbers;
})
.error(function () {
});
}
I am required to edit the Fav Numbers list on the UI. and post it back to another url through http post url method. What I am stuck is with the concept of asynchronous calls, due to which I am unable to retrieve the favorite numbers list to be available for editing. Please help!
I have tried a method of using promises as follows:
app.factory('myService', function($http) {
var myService = {
async: function(url) {
var promise = $http.get(url).then(function (response) {
console.log(response);
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return myService;
});
In my controller I am doing:
angular.module('JuryApp').controller('mycontroller', ['myService', function (myService) {
myService.async(url).then(function(d) {
$scope.data = d;
});
app.controller('MainCtrl', function( myService,$scope) {
// Call the async method and then do stuff with what is returned inside our own then function
myService.async().then(function(d) {
$scope.data = d;
});
});
But I keep getting the error 'd is not defined'. It keeps giving an error of some sort, where the debugger goes into an infinite loop or something.
You are overcomplicating it, I think. Async calls are actually pretty simple:
You're service:
app.factory("myService", ["$http", function($http) {
var MyService = {
getData: function(url) {
return $http.get(url); //$http returns a promise by default
}
};
return MyService;
})];
Your controller:
angular.module('JuryApp').controller('mycontroller', ['myService', function (myService) {
$scope.FavNumbers = [];
var url = "http://my.api.com/";
myService.getData(url).then(function(response) {
$scope.FavNumbers = response.data[0].FavNumbers;
});
}]);
That's all that you need to do.
I have an AngularJS service for a restful API:
angular
.module('app', [
])
.service('api', ['$http', '$q', function APIService($http, $q) {
this.get = function (dataProperty, params) {
return $http({
method: 'get',
url: 'https://some.api/rest/',
params: angular.extend({
default_params...
}, params)
})
.then(
function (result) {
if (result.data.status === 'ok') {
return result.data[dataProperty];
} else {
return $q.reject(angular.extend(new Error(result.data.message), { result: result.data }));
}
},
function (reason) {
return $q.reject(angular.extend(new Error('AJAX request to the API failed'), { reason: reason.data }));
});
};
}]);
I'm trying to test this api.get with the following:
describe('api', function () {
var
$httpBackend,
service;
beforeEach(module('app'));
beforeEach(inject(function (_$httpBackend_, _api_) {
$httpBackend = _$httpBackend_;
service = _api_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('', function () {
$httpBackend
.when('get', 'https://some.api/rest/')
.respond({
data: {
status: 'ok'
}
});
service.get('status', {});
$httpBackend.flush();
$httpBackend
.expect('get', 'https://some.api/rest/');
});
});
But I'm getting the error callback every time:
Error: AJAX request to the API failed in bower_components/angular-mocks/angular-mocks.js (line 279)
Am I going about setting up the test correctly? I believe the .when and .response is used to fake the actual $http call, but I can't get the success callback to fire.
The two issues were .when not looking for the correct URL (because get params were thrown in I needed to make it a regex:
.when('GET', /https:\/\/api\.flickr\.com\/services\/rest\/.*/)
Then, the .respond doesn't need to be padded with a data object, it does that for you:
.respond({ stat: 'ok' });
Can someone please tell me the best way to run tests on my controller function getData and the factory function too. I've very confused and don't know where to start. How would you write tests for the code below?
myApp.controller('myController', ['$scope', 'myFactory', function ($scope, myFactory) {
$scope.getData = function(id) {
var promise = myFactory.GetData('/dta/GetData?Id=' + id);
promise
.then(function (success) {
$scope.result = success;
}, function (error) {
$scope.error = true;
});
}
});
myApp.factory('myFactory', ['$http', function ($http) {
return {
GetData: function (url) {
return $http.get(url)
.then(function (response) {
return response.data;
}, function (error) {
return error;
});
}
}
}]);
You'll want to test each component in isolation (that's what unit tests are for). So something like this for the controller
describe('myController test', () => {
let scope, myFactory;
beforeEach(() => {
myFactory = jasmine.createSpyObj('myFactory', ['GetData']);
module('your-module-name');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
$controller('myController', {
$scope: scope,
myFactory: myfactory
});
});
});
it('getData assigns result on success', inject(function($q) {
let id = 1, success = 'success';
myFactory.GetData.and.returnValue($q.when(success));
scope.getData(id);
expect(myFactory.GetData).toHaveBeenCalledWith('/dta/GetData?Id=' + id);
scope.$digest(); // resolve promises
expect(scope.result).toBe(success);
}));
it('getData assigns error on rejections', inject(function($q) {
myFactory.GetData.and.returnValue($q.reject('error'));
scope.getData('whatever');
scope.$digest();
expect(scope.error).toEqual(true);
}));
});
For your factory, you would create a separate describe and inject and configure $httpBackend. There are plenty of example in the documentation.
FYI, you should omit the error handler in your factory, ie
return $http.get(url).then(response => response.data);
or if you don't like ES2015
return $http.get(url).then(function(response) {
return response.data;
});
as you are currently converting a failed request into a successful promise.
In fact, I'd go a bit further to make your GetData factory more useful than a mere $http wrapper
GetData: function(id) {
return $http.get('/dta/GetData', {
params: { Id: id }
}).then(function(res) {
return res.data;
});
}
Here is my factory in my app.js
app.factory('userInfoFacrory', ['$http' , "$q", function($http,$q){
return {
getNames:function(){
var differed = $q.defer();
$http.get("http://localhost/ang/api/v1/users/names")
.success(function(data) {
differed.resolve(data);
}).error(function(msg) {
differed.reject(msg);
});
return differed.promise;
}
}
}])
I use this factory in my controller like bellow , and it works fine :
app.controller('mainController', ['$scope','userInfoFacrory','$log', function($scope,userInfoFacrory,$log){
var promise = userInfoFacrory.getNames();
promise.then(function (data) {
$log.info(data); // I get my data correctly here
}, function (msg) {
$log.error(data);
})
}])
And here , I've tried to write a test unit , with karma-jasmine
describe('userInfoFacrory', function() {
var factory ,$rootScope,$scope,$q,onTaskComplete , promise;
beforeEach(function() {
module("testApp");
inject(function ($injector) {
$q = $injector.get("$q");
factory = $injector.get("userInfoFacrory");
$rootScope = $injector.get("$rootScope");
$scope = $rootScope.$new();
promise = factory.getNames(); // this function comes from my factory which returns a promise
});
});
it('should return a promise', function() {
// This test will pass , so no error so far
expect(typeof promise.then).toEqual('function');
});
});
But I can't figure out how to test to so if my promise will have my data ( that comes from my api ) or not , any suggestion would be appreciated.
thanks
it('should return a promise resolved with the http response data if the http request is successful', inject(function($httpBackend) {
var expectedData = 'fake data';
$httpBackend.expectGET('http://localhost/ang/api/v1/users/names').respond(expectedData);
var promise = factory.getNames();
var actualData;
promise.then(function(result) {
actualData = result;
});
$httpBackend.flush();
expect(actualData).toEqual(expectedData);
}));
it('should return a promise rejected with the http response data if the http request is in error', inject(function($httpBackend) {
var expectedData = 'fake data';
$httpBackend.expectGET('http://localhost/ang/api/v1/users/names').respond(400, expectedData);
var promise = factory.getNames();
var actualData;
promise.catch(function(result) {
actualData = result;
});
$httpBackend.flush();
expect(actualData).toEqual(expectedData);
}));
Working plunkr: http://plnkr.co/edit/NfO6KXWLs1QT5HG8MK0J?p=preview
Note that your code is correct, but doesn't really leverage the chaining capabilities of promises. It could simply be written as
getNames: function() {
return $http.get("http://localhost/ang/api/v1/users/names")
.then(function(response) {
return response.data;
}, function(response) {
return $q.reject(response.data);
});
};
}
Working plunkr: http://plnkr.co/edit/C5x8wRYCQ0wetjozEd0a?p=preview
I am new to Angular testing.
I am trying to test a simple method in a service that gets some data via an ajax call and returns a promise using Jasmine.
So far very unsuccessfully.
This is the method I am testing:
function getDefaultFibonnacci() {
var deferred = $q.defer();
$http({ method: 'GET', url: '/api/fibonnacci/get' })
.success(function (data) {
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject(status);
});
return deferred.promise;
}
This is my test code: (Please note all other tests pass apart from 'should return 0,1,1,2,3'
describe('datacontext', function () {
var $httpBackend;
var $rootScope;
var datacontext;
var $q;
beforeEach(function () {
module('app');
inject(function (_$httpBackend_, _$rootScope_, _$q_, _datacontext_) {
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
datacontext = _datacontext_;
$q = _$q_;
});
});
it('should have a getDefaultFibonnacci() function', function () {
expect(angular.isFunction(datacontext.getDefaultFibonnacci)).toBe(true);
});
it('should return a promise', function () {
expect(datacontext.getDefaultFibonnacci().then).toBeDefined();
});
it('should return 0,1,1,2,3', function () {
var sequence = '123';
$httpBackend.when('GET', 'app/dashboard/dashboard.html').respond('');
$httpBackend.when('GET', '/api/fibonnacci/get').respond('0,1,1,2,3');
var deferred = $q.defer();
var promise = deferred.promise;
promise.then(function (response) {
sequence = response.success;
});
datacontext.getDefaultFibonnacci().then(function (data) { deferred.resolve(data); });
$rootScope.$digest();
expect(sequence).toEqual('0,1,1,2,3');
});
});
Guys thanks for all your comments. I learnt a lot through this exercise.
This is the code I ended up with for a passing test.
function getDefaultFibonnacci() {
return $http({ method: 'GET', url: '/api/fibonnacci/get' });
}
it('should return 0,1,1,2,3', function () {
var sequence;
$httpBackend.whenGET('app/dashboard/dashboard.html').respond('');
$httpBackend.expectGET('/api/fibonnacci/get').respond('0,1,1,2,3');
datacontext.getDefaultFibonnacci().then(function (data) {
sequence = data.data;
});
$httpBackend.flush();
expect(sequence).toEqual('0,1,1,2,3');
});
$httpBackend has a flush() method for exactly this reason.
flush() simulates the http server responding, so it will trigger the resolution of your $http.get(). Until you call flush(), nothing will happen (the server hasn't responded yet).
As such, replace your $rootScope.digest() code with $httpBackend.flush() and work from there.
Furthermore, you can save a lot of effort by understanding that $http methods themselves return promises.
This:
function getDefaultFibonnacci() {
var deferred = $q.defer();
$http({ method: 'GET', url: '/api/fibonnacci/get' })
.success(function (data) {
deferred.resolve(data);
})
.error(function (data, status) {
deferred.reject(status);
});
return deferred.promise;
}
Can be simplified to this:
function getDefaultFibonnacci() {
return $http({ method: 'GET', url: '/api/fibonnacci/get' })
}
And will do the same thing.
Finally you don't need another promise in your test. This is enough (remove all reference to $q and deferred and put this directly after your $httpBackend.when(... code):
datacontext.getDefaultFibonnacci()
.then(function (data) {
sequence = data;
});
Are you expecting an object {success: "0,1,1,2,3"} as the response from the http service? You're using response.success when promise resolved
promise.then(function (response) {
sequence = response.success;
});
whereas you're returning a string '0,1,1,2,3'
$httpBackend.when('GET', '/api/fibonnacci/get').respond('0,1,1,2,3');
Also, from the code I see that you don't need to create another promise to test your method.
Try this:
it('should return 0,1,1,2,3', function () {
var sequence = '123';
$httpBackend.when('GET', 'app/dashboard/dashboard.html').respond('');
$httpBackend.when('GET', '/api/fibonnacci/get').respond('0,1,1,2,3');
datacontext.getDefaultFibonnacci().then(function (data) { sequence = data; });
$rootScope.$digest();
expect(sequence).toEqual('0,1,1,2,3');
});
The most important think you forgot to do is call $httpBackend.flush() at some point after making the requests before using the data.
Also you don't need to create an extra promise.
it('should return 0,1,1,2,3', function () {
var sequence;
// Use the shorthand method whenGET
$httpBackend.whenGET('app/dashboard/dashboard.html').respond('');
// We should probably test that this request is actually made, so use expect<method>
$httpBackend.expectGET('/api/fibonnacci/get').respond('0,1,1,2,3');
// Leave out the extra promise code.
// var deferred = $q.defer();
// var promise = deferred.promise;
// promise.then(function (response) {
// sequence = response.success;
// });
// datacontext.getDefaultFibonnacci().then(function (data) { deferred.resolve(data); });
datacontext.getDefaultFibonnacci().then(function (response) {
sequence = response.success;
});
$httpBackend.flush(); // Flush the backend. Important!
// $rootScope.$digest(); // I don't think this is necessary.
expect(sequence).toEqual('0,1,1,2,3');
});
The html template won't be called if you set up your app