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

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

Related

Trouble understanding how angularjs $httpDefaultCache, $cacheFactory and how to access that data

I'm having to inline all resources into one file and this includes all the data that my application uses. With a gulp process, I've been able to create a $cacheFactory with all the data:
angular.module('app').run('$cacheFactory', '$http', function($cacheFactory, $http){
var $httpDefaultCache = $cacheFactory.get('$http');
$httpDefaultCache.put('/data/names.json',{...});
$httpDefaultCache.put('/data/places.json',{...});
});
My understanding of how to access this instead of making a call externally (file) may be incorrect.
I thought that by setting $httpProvider.defaults.cache = true, that my request to the endpoints of above would use the default cache.
.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.cache = true;
}]);
Instead I get an error of
https://.../data/names.json 404 (Not Found)
As if it is looking within the the client and not in angular's cache. The type is also an xhr.
data.load(names, '/data/names.json');
...
function load(broadcastName, url, force){
if(!isLoaded[broadcastName] && !force){
console.log('Loading name data from', url);
// Make the http GET request
http({
method: 'GET',
url: url,
cache: true
}).then(function success(response){
console.log("response: ", response)
...
})
Had to create a custom http request that perfectly imitates angular $http service. It is used in several other applications we have, so I know it works. Only thing that has been added for this implenation is cache:true.
I've looked at several other questions that were similar, but I am still not understanding how it is working. How does using http default cache work? And is there something I should be aware of that I may be glossing over?
I hope this makes sense.
There's no special term for $http cache like '$httpDefaultCache'. It works like you expect it to work. You set either $httpProvider.defaults.cache or cache request option to true, and the response is retrieved from $http cache, which is available by default as $cacheFactory.get('$http').
Cache is just key/value storage. If request URL doesn't match a key completely, cache won't be used.
Here's an example how it works:
$cacheFactory.get('$http').put('/foo', 'foo');
$http({ url: '/foo', cache: true })
.then(result => {
$scope.foo = result.data;
})
.catch(result => console.error(result))

Mock $http with configuration parameters

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

Angular JS 1.X Access Resolved Promise Objects values

I have read a lot of posts on promises,resolving promises, and accessing the data however I cannot seem to. Following some posts on Stack Overflow has just caused errors, so I am not sure what exactly I am doing wrong.
I have a function like so:
function getJsonl() {
var deferred = $q.defer();
$http({
url: 'urlNotShownForSecurity',
dataType:"json",
method: 'GET',
data:{"requestId":"123"},
headers:{"Content-Type":"application/json","requestId":"123"},
}).success(function(data) {
deferred.resolve(data);
console.log(data)
}).error(function(error,status,headers,config) {
deferred.reject(error);
});
return Promise.resolve(deferred.promise);
}
Here I return a json promise that has been resolved resulting in a json object I believe.
Printing to console I get the following:
Inside data is the information I need, it looks like this:
data:Array[8]
0:Object
description:"My description paragraph"
I have tried things with the returned object in my controller like:
vm.result = data.data[0].description;
vm.result = data[0].description
I have tried many different approaches in the view as well to access but I get 2 blank li tags and that is it.
I would like to be able to access the data so I populate a table. So if I can use it with ng repeat that would be great, as well as being able to access without because some data is used in more than just the table.
Update
#DanKing, following your implementation I get the following output in console:
Now I am back with a promise object.
It looks to me as though you're fundamentally misunderstanding the nature of promises.
$http() is an asynchronous function - that means it doesn't complete straight away, which is why it returns a promise.
It looks to me as though you're trying to call $http() and then get the result back and return it from your getJson1() method, before $http() has finished executing.
You can't do that. Your getJson1() method should just return the promise, so your calling method can chain onto it - like this:
getJson1().then(function(data) {
// do some other stuff with the data
});
The whole point of promise chains is that they don't execute straightaway - instead you provide callback functions that will be executed at some indeterminate point in the future, when the previous operation completes.
Your getJson1() function just needs to do this:
return $http({
url: 'urlNotShownForSecurity',
dataType:"json",
method: 'GET',
data:{"requestId":"123"},
headers:{"Content-Type":"application/json","requestId":"123"},
});
getJsonl().then(function(data){
console.log(data);
},function(err){
console.log(err);
})
should work. Where is your $http request and where is your call to getJsonl() will also make a difference. So choose that carefully when implementation. If you are using this in a service then you will have to return the function result say
this.somefunction = function (){
return getJonl();
}
and in your controller inject the service and do the following
service.somefunction().then(function(data){
console.log(data);
},function(err){
console.log(err);
})
Ok, rewrote the answer as a complete component to show the moving parts.
$http returns a promise so your original getJsonl call can be simplified. Using your original $http parameters this should dump your API response to the screen if you use the <json-thing> tag:
angular.module('yourModuleName')
.component('jsonThing', {
template: '<pre>{{$ctrl.thing|json}}</pre>',
controller: function ($http) {
var $ctrl = this;
getJsonl()
.then(function (response) {
console.log(response); // we have the $http result here
$ctrl.thing = response.data; // allow the template access
}, function (error) {
console.log(error); // api call failed
});
// call backend server and return a promise of incoming data
function getJsonl() {
return $http({
url: 'urlNotShownForSecurity',
dataType: 'json',
method: 'GET',
data: { requestId: '123'},
headers: { 'Content-Type': 'application/json', requestId: '123'}
});
}
}
});

angular-http-auth with $http transformResponse

I'm using angular-http-auth to show a login dialog whenever a 401 "unauthorized" response is returned from the server.
Since I'm cool, I also try to deserialize response objects in my services. For example, if a service requests a car and the response is {make: Honda, model: Civic}, I try to deserialize that into a Car object using transformResponse.
For example:
getCar: function() {
return $http.get('/api/car', {
method: 'GET',
transformResponse: function(data, headers) {
var c = angular.fromJson(data);
return new Car(c);
}
});
}
This doesn't work with angular-http-auth. If the response was a 401 Unauthorized, you'll get a javascript error. It's because angular will try to run that transformResponse code even if the response was a 401.
It turns out that $http interceptors (which is what angular-http-auth uses) are run AFTER the transformResponse code. That's a huge problem, because none of that code in transformResponse will work if the server response was a 401 (there wouldn't be any data)
Is this a problem for anyone else? How did you get around it? Am I not to use transformResponse if I use $http interceptors?
Late to the party, I know, but to anyone coming here from Google like I did (I also posted this as a comment on a related issue filed with the Angular repo):
I also found it to be confusing that response interceptors run after the transformResponse method. I added a method to $http.defaults.transformResponse. Here is an example from the documentation on how to do that.
So, if you need to basically have a response interceptor that runs before the transformResponse method, this should do it:
'use strict';
angular.module('app')
.run(function ($http) {
$http.defaults.transformResponse.push(function (data, headers) {
// do stuff here before the response transformation
// Be sure to return `data` so that the next function in the queue can use it.
// Your services won't load otherwise!
return data;
});
});
If your services or http calls don't have their own response transformer, you're good now.
If your services do have their own transformResponse method, they will actually override all default transformers (I found this out after a long read of the documentation), and the above code will not run.
To circumvent this, you can follow this example in the docs.
To get around this problem I don't use transformResponse anymore. I just can't see the point of transformResponse if it runs before $http interceptors.
In order to use angular-http-auth and also deserialize responses in your services, you can write your services so that they execute the HTTP request first and then deserialize the response in a callback function.
As an example, here is how I would have restructured the example in the OP:
Plunker
services.factory('HttpCarService', function($resource, $q) {
var resource = $resource('/api/car');
return {
getCar: function() {
var deferred = $q.defer();
var car = null;
var successCallback = function(data, status, headers, config) {
var c = angular.fromJson(data);
car = new Car(c);
deferred.resolve(car);
};
var errorCallback = function(data, status, headers, config) {
deferred.reject("something wrong");
};
var result = resource.get(successCallback, errorCallback);
return deferred.promise;
}
};
});
This pattern will also work if data is an array.
$http interceptors will run before either of the callback methods are executed. If your $resource needs url params, you can make the getCar() function accept a config object as a parameter, and pass the necessary information on when you make the $resource.get() call.

Testing angular resources the right way

My resource looks something like this.
return $resource(baseURL + 'user',{},{
isPermitted: {method: 'POST', isArray:false, params: { regID: #regID} },
doesExist: {method: 'GET', url: baseURL + 'user/doesExist' }
});
I have written Jasmine tests for the same.
What I am trying to understand is
Is this the correct way to layout the test (or should i be using something like sinon)
Are these the only test that need to be performed on a resource (or should i be writing a lot more tests. Kindly point out to what other aspects need to be tested)
The test:
describe('UserCheck',function(){
var $httpBackend, mockUserCheckResource;
var webServiceBaseURL = 'server.comp.com';
beforeEach(module('demo'));
beforeEach(function(){
angular.mock.inject(function($injector){
$httpBackend = $injector.get('$httpBackend');
mockUserCheckResource = $injector.get('UserCheck');
});
});
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('isPermitted',function(){
var aObj = {regID:'xxx'};
it('should issue a POST request to /user',function(){
var result;
$httpBackend.expectPOST(webServiceBaseURL + 'user',{regID:'xxx'}).respond(201);
result = mockUserCheckResource.isPermitted(aObj);
$httpBackend.flush();
expect(result.regID).toBeDefined('xxx');
});
});
describe('doesExist',function(){
it('should issue a GET request to /user/doesExist',function(){
var result = {};
$httpBackend.expectGET(webServiceBaseURL + 'user/doesExist?userID=123').respond({"isPresent":1});
result = mockUserCheckResource.doesExist({userID:'123'});
$httpBackend.flush();
expect(result.isPresent).toBe(1);
});
});
);
Two things here : you can test whether $resource properly maps whatever instructions it is given to HTTP calls, which is kind of pointless, and you can also test that your application has a service called UserCheck which maps two methods (isPermitted and doesExist) to the proper HTTP calls. These are different. For the latter — which definitely does make sense — your tests are allright (and offer a good coverage of what your code does in terms of inputs (method calls, HTTP requests), not how it does it.
Still, you can simplify a bit and make your tests clearer :
describe('UserCheck',function(){
var $httpBackend = undefined;
var UserCheck = undefined;
beforeEach(module('demo'));
beforeEach(inject(function(_$httpBackend_, _UserCheck_) {
$httpBackend = _$httpBackend;
UserCheck = _UserCheck_;
}));
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('isPermitted',function(){
it('should issue a POST request to /user',function(){
$httpBackend.expectPOST('server.comp.com/user').respond(200);
UserCheck.isPermitted({});
$httpBackend.flush();
});
});
describe('doesExist',function(){
it('should issue a GET request to /user/doesExist',function(){
$httpBackend.expectGET('server.comp.com/user/doesExist?userID=123').respond(200);
UserCheck.doesExist({userID:'123'});
$httpBackend.flush();
});
});
});
Tips :
name your services the same in your code and in your tests
don't specify the values returned by $httpBackend unless you want to test how your service/controller will handle them
always use plain text URL in your $httpBackend's expectations : it allows to quickly map a method and an URL, and also allows to search across the test (like 'where does this kind or URL gets called ?')
no global state, even in tests (except for injected dependencies). If your test grows bigger, it'll be a pain to refactor and handle several different use cases, which is basiclaly the purpose of a unit test file.

Resources