testing a service's call to $http with $httpBackend - angularjs

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' });

Related

Jasmine $HTTP requests with mock data

Im trying to test $http requests and no matter what I do I cant seem to get it to work. I have a factory that holds the 4 request type GET, POST, PUT, DELETE.
Im trying to test the response from a get request but my scope variable that is assigned the response in the controller is giving an error or undefined = the response from the get request.
The factory code is as follows:
app.factory('requestService', ['$http', function ($http) {
// reusable requests
return {
//reuable get request
getRequest: function (url) {
return $http({
method: 'GET',
dataType: "json",
url: url
});
},
//reuable post request
postRequest: function (url, data) {
return $http({
method: 'POST',
data: data,
url: url
});
},
//reuable put request
putRequest: function (url, data) {
return $http({
method: 'PUT',
data: data,
url: url
});
},
//reuable delete request
deleteRequest: function (url) {
return $http({
method: 'DELETE',
url: url
});
}
}
}]);
The following is in the controller.
//get the teams
$scope.getTeams = function(){
requestService.getRequest('https:.../teams').then(function (response) {
$scope.teams = response;
});
}
$scope.getTeams();
Jasmine code:
describe('Controller Test', function() {
var $rootScope, controller;
//Tell the $httpBackend to respond with our mock object
var teamsResponse = {// Teams Data //};
beforeEach(function() {
module('myApp')
});
beforeEach(inject(function($rootScope, $controller, _$httpBackend_, $http) {
$scope = $rootScope.$new();
controller = $controller;
controller('Controller', {$scope: $scope});
$httpBackend = _$httpBackend_;
$httpBackend.whenGET('https:...../teams').respond(200, teamsResponse);
}));
it('should load a list of teams', function() {
$scope.getTeams();
$httpBackend.flush();
expect($scope.teams).toBe(teamsResponse);
});
});
The error I get is:
Expected undefined to be {// Teams Data //}
describe("Testing Common Get JSON Service", function () {
var service, $httpBackend;
beforeEach(inject(function($injector,$rootScope) {
service = $injector.get('CommonService');
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
successCallback = jasmine.createSpy();
errorCallback = jasmine.createSpy();
$httpBackend.when('GET', getFlightIncidentLookupUrl).respond(getFlightIncidentLookup);
successCallback();
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('test getServiceJSON works fine and calls a GET method', function () {
service.getServiceJSON(getFlightIncidentLookupUrl).then(function(response) {
expect(response.data.incidentLookup.length).toEqual(1);
});
$httpBackend.flush();
});
});
This worked for me . here CommonService is a service in Angular js which calls a get method. I got the response correctly.

$httpBackend expectGET not returning expected data

I will just start out saying that $httpBackend and spyOn are pretty new to me, and I am struggling to get a clear picture of what is happening. Basically, I'm trying to do an $httpBackend.expectGET('/myroute/').respond(...) and then use spyOn, but I keep getting
Expected [ ] to equal Object({ success: true, message: 'Yeah!' })
Here's the code I'm trying to test:
angular.module('TestModule')
.service('TestService',TestService);
TestService.$inject = ['$http'];
function TestService($http){
var service = {};
service.DoIt = DoIt;
return service;
function DoIt() {
return $http.get('/myroute/')
.then(handleSuccess,handleError('Error happened!'));
}
function handleSuccess(res) {
return res.data;
}
function handleError(error) {
return function(){
return {success: false, message: error}
};
}
}
Here's my Karma-Jasmine test:
describe('Test Module',function(){
beforeEach(module('TestModule'));
describe('TestService Tests',function(){
var service,$httpBackend;
beforeEach(inject([
'$injector',
function($injector){
$httpBackend = $injector.get('$httpBackend');
service = $injector.get('TestService');
}
]));
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should DoIt',function(){
var mockResponse = {
data : {success:true, message:"Yeah!"}
};
$httpBackend.expectGET('/myroute')
.respond(Promise.resolve(mockResponse));
spyOn(service,'DoIt').and.callThrough();
service.DoIt().then(function(data){
expect(service.DoIt).toHaveBeenCalled();
expect(data).toEqual(mockResponse.data); <-- ERROR OCCURS HERE -->
});
$httpBackend.flush();
});
});
});
Further information:
Using the example I found here: http://www.bradoncode.com/blog/2015/06/26/unit-testing-http-ngmock-fundamentals/, I tried this in my test code:
it('should DoIt',inject(function($http){
var promise = $http.get('/myroute').then(handleSuccess,handleError('bad stuff happened'));
promise.then(function(data){
console.log('Data: ' + JSON.stringify(data,null,3));
});
$httpBackend.expect('GET','/myroute').respond(200,{data:'Yeah!'});
$httpBackend.flush();
}));
function handleSuccess(response) {return response.data;}
function handleError(error){
return function(){
return {success:false,message:error};
};
}
This gives me the response that I expect: 'Data: {"data": "yeah"}'
My updated question: I thought service.DoIt() was doing the same thing as the promise. What is going on here? Am I not injecting my service? I do not understand how Karma expects me to stage this test. Any help, please!
If I understand your intention with the test correctly, I believe this is what you need:
describe('Test Module', function() {
var $httpBackend, service;
beforeEach(module('TestService'));
beforeEach(inject(function(_$httpBackend_, _TestService_) {
$httpBackend = _$httpBackend_;
service = _TestService_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should DoIt', function() {
var mockResponse = {
data: { success: true, message: "Yeah!" }
};
$httpBackend.expectGET('/myroute/')
.respond(mockResponse);
service.DoIt().then(function(data) {
expect(data).toEqual(mockResponse);
});
$httpBackend.flush();
});
});
In my case there was transformResponse function on $resource that modified the expected mock data:
var resource = $resource('api/data/:id', {}, {
get: {
method: 'GET',
isArray: true,
transformResponse: function (data) {
var d = angular.fromJson(data);
return d.members;
}
}
...
My mock data didn't contain memebers...

How do I mock $http in AngularJS service Jasmine test?

I am trying to test an AngularJS service carService, but the $httpBackend does not seem to work.
//carService
angular.module('services').factory('carService',
function($http) {
return {
getTypes: function() {
return $http.get('/api/cars/types');
}
};
});
Can anybody explain why the response is null?
describe("Services", function () {
beforeEach(module("app.services"));
describe("Car services", function () {
var service, $httpBackend;
beforeEach(inject(function($injector) {
service = $injector.get('carService');
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('GET', "/api/cars/types").respond(["Toyota", "Honda", "Tesla"]);
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('getTypes - should return 3 car manufacturers', function () {
service.getTypes().then(function(response) {
expect(response.length).toEqual(3); //the response is null
});
$httpBackend.flush();
});
});
});
Try this:
expect(response.data.length).toEqual(3);
The response object returned by a $http request has the response data within the data property (docs).

Implementing Promises in angularJS

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.

Angular Jasmine test response interceptor

I'm trying to test my response interceptor but I have a hard time figuring out how to mock the $window object. Here is my interceptor code :
'use strict';
angular.module('Domain.handlers')
.config(function($httpProvider) {
$httpProvider.responseInterceptors.push('UnauthorizedInterceptor');
})
.factory('UnauthorizedInterceptor', function($q, $injector, $window, ENV) {
return function(promise) {
var success = function(response) { return response; };
var error = function(response) {
if (response.status === 401) {
$window.location.href = ENV.account + '/oauth/authorize?client_id=' + ENV.clientId + '&redirect_uri=' + ENV.app + '/oauth/callback&response_type=token';
}
return $q.reject(response);
};
return promise.then(success, error);
};
});
And here is my spec :
'use strict';
describe('Domain.handlers.response', function() {
var UnauthorizedInterceptor,
httpProvider,
$httpBackend,
$http,
token = '123456789';
beforeEach(module('Domain.handlers', function($httpProvider) {
httpProvider = $httpProvider;
}));
beforeEach(inject(function(_UnauthorizedInterceptor_, _$httpBackend_, _$http_) {
UnauthorizedInterceptor = _UnauthorizedInterceptor_;
$httpBackend = _$httpBackend_;
$http = _$http_;
}));
describe('UnauthorizedInterceptor', function() {
it('should be defined', function() {
expect(UnauthorizedInterceptor).toBeDefined();
});
describe('HTTP status', function() {
describe('is 200 OK', function() {
it('should return a 200 status', function() {
$httpBackend.expectGET('http://api.domain.com/clients').respond(200, {});
$http.get('http://api.domain.com/clients');
$httpBackend.flush();
});
});
describe('is 401 Unauthorized', function() {
it('should redirect to accounts.domain.com', inject(function($window) {
$httpBackend.expectGET('http://api.domain.com/clients').respond(401, {});
$http.get('http://api.domain.com/clients');
expect($window.location.href).toEqual('http://accounts.domain.com/oauth/.....');
$httpBackend.flush();
}));
});
});
});
});
I've got a : Expected 'http://localhost:8080/context.html' to equal 'http://accounts.domain.com/oauth/.....'. Any help on how to mock properly the $window object or more generally how to test a 401 + redirection case?
You should structure your interceptor definition using the more recent syntax. Your URL construction should also be in a service so that it can easily be mocked in tests.
.factory('UnauthorizedInterceptor', function($q, $window, OtherService) {
var service = {
responseError: handleUnauthorized
};
return service;
function handleUnauthorized(rejection) {
if (rejection.status === 401) {
$window.location.href = OtherService.getUnauthorizedRedirectURL();
}
return $q.reject(rejection);
}
});
Doing so will let you test it just like any other factory without having to worry about the internal implementations of $http interceptors, or having to mock responses with $httpBackend.
describe('Domain.handlers.response', function() {
var $window,
UnauthorizedInterceptor,
OtherService,
redirectUrl = 'someUrl';
beforeEach(module('Domain.handlers'));
beforeEach(function () {
$window = { location: { href: null } };
module(function($provide) {
$provide.value('$window', $window);
});
});
beforeEach(inject(function(_UnauthorizedInterceptor_, _OtherService_) {
UnauthorizedInterceptor = _UnauthorizedInterceptor_;
OtherService = _OtherService_;
spyOn(OtherService, 'getUnauthorizedRedirectURL').andReturn(redirectUrl);
}));
describe('UnauthorizedInterceptor', function() {
it('should be defined', function() {
expect(UnauthorizedInterceptor).toBeDefined();
});
it('should have a handler for responseError', function () {
expect(angular.isFunction(UnauthorizedInterceptor.responseError)).toBe(true);
});
describe('when HTTP 401', function () {
beforeEach(function () {
var rejection = { status: 401 };
UnauthorizedInterceptor.responseError(rejection);
});
it('should set window location', function () {
expect($window.location.href).toBe(redirectUrl);
});
});
describe('when not HTTP 401', function () {
beforeEach(function () {
var rejection = { status: 500 };
UnauthorizedInterceptor.responseError(rejection);
});
it('should not set window location', function () {
expect($window.location.href).not.toBe(redirectUrl);
});
});
});
});
Here is an example of the responseError interceptor and the corresponding jasmine spec.
angular.module('interceptorDemo').factory('redirectInterceptor', ['$q', '$window', function($q, $window) {
'use strict';
function handleUnauthorizedAccess(config) {
if (401 === config.status) {
$window.location = '/signIn/';
}
return $q.reject(config);
}
return {
responseError: handleUnauthorizedAccess
};
}]);
The interceptor intercepts the ajax request, if the request is failed, then if the status code is 401 then user is redirected to signIn page.
Jasmine spec for the same is:
describe('redirectInterceptor specs', function() {
var redirectInterceptor, $q;
beforeEach(module('interceptorDemo'));
beforeEach(function() {
$window = {
location: {
href: null
}
};
module(function($provide) {
$provide.value('$window', $window);
});
});
beforeEach(inject(function(_redirectInterceptor_, _$q_) {
redirectInterceptor = _redirectInterceptor_;
$q = _$q_;
spyOn($q, 'reject');
}));
describe('redirectInterceptor specs', function() {
it('should redirect to signIn page for unauthorized access', function() {
var response = {
status: 401,
config: {}
};
var promise = redirectInterceptor.responseError(response);
expect($window.location).toBe('/singIn/');
expect($q.reject).toHaveBeenCalled();
});
it('should not redirect to signIn page for error code other than unauthorized access', function() {
var response = {
status: 404,
config: {}
};
var promise = redirectInterceptor.responseError(response);
expect($window.location).toEqual({
href: null
});
expect($q.reject).toHaveBeenCalled();
});
});
});
We have spied on the $q so we can also test that the reject is called for the 401 error.

Resources