Mock $http with configuration parameters - angularjs

I'm applying some tests in an existing AngularJS application in order to ensure it's correct behaviour for future changes in the code.
I am pretty new with Jasmine & Karma testing, so I've decided to start with a small and basic service which performs an http request to the backend, and waits for the result with a promise, nothing new.
Here's the service method to test:
function getInformedConsent(queryParameters) {
var def = $q.defer(),
httpParameters = {
url: ENV.apiEndpoint + '/urlResource',
method: 'GET',
params: queryParameters,
paramSerializer: '$httpParamSerializerJQLike'
};
$http(httpParameters)
.then(
function (response) {
def.resolve(response);
},
function (error) {
def.reject(error);
}
);
return def.promise;
}
And here my test:
it('getInformedConsent method test', function() {
$httpBackend.expectGET(/.*\/urlResource?.*/g)
.respond(informedConsentJson.response);
var promise;
promise = InformedconsentService.getInformedConsent(informedConsentJson.queryParameters[0]);
promise
.then(function(response) {
console.log(response);
expect(response).toEqual(informedConsentJson.response);
});
$httpBackend.flush();
});
informedConsentJson as you can supose, is a fixture with input and the expected output.
Reading AngularJS documentation, I decided to use $httpBackend, because it's already a mock of $http service, so I thought it could be useful.
The problem is that somewhere in the code, someone is broadcasting a "$locationChangeStart" event and executing
$rootScope.$on('$locationChangeStart', function (event,current,old) {
/* some code here */
});
in app.js.
I'm not trying to change the URL, i'm just trying to get some data from the mocked backend.
I asume that is because I'm not using $http mock ($httpBackend) as it should be used.
Anyone can help me with $http with configuration JSON mock?
It's freaking me out.
Thank you all in advance for your time and your responses

Related

Mock angular service data response

I have an Api service which is in charge of controlling all my http requests. GET, POST, PUT, DELETE...
I'm trying to write some unitTests and I get a problem with the following scenario.
self.Api.post('/myEndpoint/action/', actionData)
.then(function(resp){
result = _.get(resp, 'data.MessageList');
if(resp.status = 200 && result) {
setActionResults(resp.data);
}
});
I want to mock in my unitTest the resp. What should I do? Must I mock the httpBackend service as here http://plnkr.co/edit/eXycLiNmlVKjaZXf0kCH?p=preview ? Can I do it in other way?
Using httpBackend is the way to go, mocking each request made by your application will work just fine. However you can mock your entire service as well, and unit test using the mocked service instead of the original. Regardless, httpBackend is much more simple to handle that (for http request services) than creating a new service with the same interface of the original. But in some case, you may need to control what your services are doing, therefore you will have to use service mocking.
For example:
angular.module('myApp')
.service('DataService', function ($http) {
this.getData = function () {
return $http.get('http://my.end.point/api/v1/data')
.then(function (response) {
return response.data;
});
};
});
angular.module('myAppMock')
.service('MockedDataService', function ($q) {
this.getData = function () {
return $q.resolve({ data: 'myData' }); // you can add a delay if you like
}
});

how to have angularJS post data to MVC controller which redirects to a view

I am posting some data to an MVC action method using AngularJS. This action method will either show its backing view or redirect to another page. Currently all that is happening is the data is getting posted but the redirect is not happening via MVC. I am getting this done using angular's window.location method. I want to know if there is a better way or if I need to post differently using Angular.
On page A I have angular scripts posting data to page B like below:
serviceDataFactory.POST('http://localhost:1234/home/B', someData, pageConfig).then(function () {
//on success
window.location = 'http://localhost:1234/home/Index';
},
function() {
//on error
window.location = 'http://localhost:1234/home/B';
});
This is my service factory
app.factory('serviceFactory', function($http, $q) {
var service = {};
//POST
service.POST = function (url, postData, conf) {
var d = $q.defer();
$http({
method: 'POST',
url: url,
data: postData,
config: conf
}).success(function(data) {
d.resolve(data);
}).error(function(error) {
d.reject(error);
});
return d.promise;
}
return service;
}
);
On Page B I want to redirect to another page. This is my page B in MVC
[HttpPost]
public ActionResult B(string someData)
{
//recieve string someData and perform some logic based on it
.
.
.
if(boolCondition)
return RedirectToAction("Index", "Home");
else
return View();
}
Here once Angular posts to the action method B, it executes all the code all the way till the if(boolCondition) statement. Since I am unable to have that redirect affected via MVC, I do that in Angular itself using the success or error block that the promise returns to.
I want to know if there is a better way to do this or if I am doing something wrong here or if this is the only acceptable way. How do I get angular to hand-off to the MVC action method and let further redirects continue from there only?
You should not use the .success() / .error() pattern with $http, because this has been deprecated. Instead, use then() with two arguments, the first argument being the success function and the second being the error function.
The $http legacy promise methods success and error have been
deprecated. Use the standard then method instead. If
$httpProvider.useLegacyPromiseExtensions is set to false then these
methods will throw $http/legacy error.
You do not need to promisify the result of $http, because $http returns a promise. Just return $http from your service.
app.factory('serviceFactory', function($http, $q) {
var service = {};
//POST
service.POST = function (url, postData, conf) {
return $http({
method: 'POST',
url: url,
data: postData,
config: conf
});
}
return service;
});
Your Page A controller will work the same as before with this new simplified code. At the server, be sure to emit a 500 http status code in cases where you want to trigger the
function() {
//on error
window.location = 'http://localhost:1234/home/B';
}
to run. The 500 in the headers of the response will cause the AngularJS promise to run the second function in your controller.

$httpBackend doesn't respond in Protractor test

I'm trying to write a test in Protractor/Jasmine that depends upon my being able to see the headers sent in an HTTP request. To that end I'm trying to create a mock endpoint with $httpBackend that will respond to a call with the headers themselves, allowing me to look into them in my test. My code is as follows:
describe('Headers', function () {
it('should include X-XSRF-TOKEN on HTTP calls', function () {
browser.addMockModule('httpBackendMock', function () {
angular.module('httpBackendMock', ['CorsApp', 'ngMockE2E'])
.run(function ($httpBackend) {
$httpBackend.whenGET('/xsrftest')
.respond(function (method, url, data, headers) {
return headers;
});
})
});
loginPage.get();
browser.executeAsyncScript(function (callback) {
var $http = angular.injector(['ng']).get('$http');
$http.get('/xsrftest')
.then(function (response) {
callback(response);
});
})
.then(function (response) {
console.log(response);
});
});
});
I've tried to follow the patterns set out in many resources for utilizing $httpBackend in protractor testing. However, when I run this test, I get a Protractor timeout. It seems as though the $http.get call never receives a response, hence the callback is never called and so the executeAsyncScript call times out. If I put in a dummy call to the callback that's not dependent on the $http.get, it works as expected.
What am I doing wrong in setting up $httpBackend? How can I get it to respond to my $http request?
Thanks!

$httpBackend mock responds with more than what I tell it to?

I have a Jasmine test for one of my Angular services which fetches data from a server and makes it publicly accessible. The server returns JSON, just an array of objects [{...}, {...}, ...].
Now I tried writing a test with a mocked out http backend:
var mockLibrary = [{}, {}];
beforeEach(inject(function(LibrarySvc) {
libSvc = LibrarySvc;
}));
it('should fetch the library', function(done) {
inject(function($httpBackend) {
$httpBackend.expectGET('/library').respond(200, mockLibrary);
libSvc.getLibrary()
.then(function(response) {
expect(_.isEqual(response, mockLibrary)).toBeTruthy();
})
.finally(done);
$httpBackend.flush();
});
});
The code in my service is as follows:
var library;
var deferred = $q.defer();
$http.get('/library')
.then(function(data) {
library = data;
deferred.resolve(library);
}, function(err) {
deferred.reject(err);
});
So, the service assigns the JSON response body of the server to an internal library variable and resolves the promise with it. Now when running this test, it fails, because response in the test is not equal to mockLibrary. Instead, response is this:
Object{data: [Object{}, Object{}], status: 200, headers: function (c){ ... }, config: Object{method: 'GET', transformRequest: [...], transformResponse: [...], url: '/library', headers: Object{Accept: ...}}, statusText: ''}
and what I actually wanted would now be response.data in the test, and data.data in my service logic.
Why does Angular do this? Is this implicitly suggesting that JSON responses from my server should always be a hash that holds the actual response data in a data attribute? Why would I ever want to adjust my service logic to an Angular test? Or did I miss something? The docs for $httpBackend weren't helpful on this matter.
Further notes: I'm using underscore.js for a deep equals check at _.isEqual(...), and Jasmine 2.0 to test asynchronously (thus the function(done) syntax).
When using .then() for the $http, the first parameter won't be the data from your server.
It will be a wrapper object like you see and the data from your server will be in .data.
There is no different between your service and the test. You might be confused and think that the data parameter in your service is the data from your server, it isn't.
Therefore, yes, you have to use response.data in your test and data.data in your service. But I suggest you rename the data parameter in your service to response for consistency.
Also see: $http documentation

Delay an angular.js $http service

I have some angular factories for making ajax calls towards legacy ASP.NET .asmx web services like so:
module.factory('productService', ["$http",
function ($http) {
return {
getSpecialProducts: function (data) {
return $http.post('/ajax/Products.asmx/GetSpecialProducs', data);
}
}
} ]);
I'm testing on a local network so response times are "too" good. Is there a smart way of delaying the $http a couple of seconds from making the call to simulate a bad connection?
Or do I need to wrap all calls to the factory methods in a $timeout ?
$timeout(function() {
productService.getSpecialProducs(data).success(success).error(error);
}, $scope.MOCK_ajaxDelay);
Interesting question!
As you mentioned yourself, $timeout is the most logical choice for a delayed call. Instead of having $timeout calls everywhere, you could push a response interceptor that wraps the $http promise in a $timeout promise, as conceptually outlined in the documentation of $http, and register it in one of your configuration blocks. This means all $http calls are affected by the $timeout delay. Something along the lines of:
$httpProvider.interceptors.push(function($timeout) {
return {
"response": function (response) {
return $timeout(function() {
return response;
}, 2500);
}
};
});
As a bonus to your "to simulate a bad connection?", you could reject or do absolutely nothing randomly, too. Heh heh heh.
The new chrome device emulator has a network throttling function:
To get there: In Google Chrome, press F12 to open the Developer Tools. Then, on the top left corner, click the "Toggle device mode" icon (left to the "Elements" menu).
Developing more on the answer of #stevuu
responseInterceptors seems to be depreceted (as of 1.2.20) I have modified the code to work on the interceptors mechanism:
$httpProvider.interceptors.push(function($q, $timeout) {
return {
'response': function(response) {
var defer = $q.defer();
$timeout(function() {
defer.resolve(response);
}, 2300);
return defer.promise;
}
};
});
You could use the $q service for defer().promise pattern:
function someFunction(MOCK_ajaxDelay) {
var deferred = $q.defer();
$http.post('/ajax/Products.asmx/GetSpecialProducs', data).success(function(response) {
$timeout(function() {deferred.resolve({ success: true, response: response })}, MOCK_ajaxDelay);
}).error(function() {
$timeout(function() {deferred.resolve({ success: true, response: response } }, MOCK_ajaxDelay);
});
return deferred.promise;
}
someService.someFunction(500).then(function(data) {
if (data.success) {
$scope.items = data.response.d;
}
});
But if you are really mock testing, the better solution is to look into ngMock: http://docs.angularjs.org/api/ngMock.$httpBackend
While #stevuu's answer is correct, the syntax has changed in the newer AngularJS versions since then. The updated syntax is:
$httpProvider.interceptors.push(["$q", "$timeout", function ($q, $timeout) {
function slower(response) {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(response);
}, 2000);
return deferred.promise;
}
return {
'response': slower
};
}]);
You can achieve this using the promise api combined with a $timeout. The $http.post function returns a promise from which you can call .success and .error (these are http specific methods). This promise is resolved when the http request is complete. If you build your own promise then you can tell it to delay 2 seconds and then resolve when the http request is complete:
module.factory('productService', function ($http, $q, $timeout) {
return {
getSpecialProducts: function (data) {
var defer = $q.defer();
$http.post('/ajax/Products.asmx/GetSpecialProducs', data).success(
function(data) {
// successful http request, resolve after two seconds
$timeout(function() {
defer.resolve(data);
}, 2000)
}).error(function() {
defer.reject("Http Error");
})
return defer.promise;
}
}
});
But note - you will have to use promise.then(successCallback, errorCallback) functionality - that is, you'll lose the ability to access http headers, status & config from your controllers/directives unless you explicitly supply them to the object passed to defer.resolve({})
Links:
Defer/Promise Api
Http/Promise Api
Resolve egghead video
In response to the testing aspect of your question, Fiddler has a really useful function that helps when you need to simulate delays:
Click on the AutoResponders tab in Fiddler.
Add a rule with a regex that matches the URL of the request you want to delay.
Set the "respond with" to "*delay:1000" where the number is the delay in milliseconds.
The AutoResponder functionality in Fiddler is extremely useful for testing JS that involves a lot of http requests. You can set it to respond with particular http error codes, block responses, etc.
If you are using a service that returns a promise, then inside you should put a return before the $timeout as well because that returns just another promise.
return dataService.loadSavedItem({
save_id: item.save_id,
context: item.context
}).then(function (data) {
// timeout returns a promise
return $timeout(function () {
return data;
},2000);
});
Hope it helps someone!

Resources