Restangular usage in a service +examples and unit tests

Restangular (https://github.com/mgonto/restangular) is a very nice replacement for Agular’s http://docs.angularjs.org/api/ng.$http service. It has a fairly straightforward usage, documentation is OK, and I believe, it is a common choice among Angular developers.

Let’s say we have an Angular controller, and we want to expose a web resource to our $scope. Code can look something like that:

Restagular.one('someVariable').get({someParameter: $stateParams.someParameter})
.then(function(result){
$scope.someVariable = result;
});
view raw gistfile1.js hosted with ❤ by GitHub

If you want to simplify things, you can use the fact that Restangular returns a promise, and Angular can handle promises transparently. This can shorten your code to that:

$scope.someVariable = Restagular.one('someVariable').get({someParameter: $stateParams.someParameter});
view raw gistfile1.js hosted with ❤ by GitHub

While this is all fine and dandy, it is possible that in some cases, we would like to make things more object-oriented. Restangular allows us to enhance obtaining resources: https://github.com/mgonto/restangular#creating-new-restangular-methods. However, while Restangular proclaims itself as a service, in a traditional MVC  sense, it is just a http://en.wikipedia.org/wiki/Data_access_object with enhanced capabilities. So, a purist like me would feel uncomfortable using that hybrid dao/service approach. Wouldn’t it be slightly better to define a service that handles obtaining someVariable objects? This service itself can depend on Restangular for actually making the rest call. So the below code:

.controller('MyController', ['Restangular', '$scope', '$stateParams', function(Restangular, $scope, $stateParams){
$scope.someOtherVariables = Restagular.all('someOtherVariables').getList({
someOtherParameter: $stateParams.someOtherParameter,
someEvenOtherParameter: $stateParams.someEvenOtherParameter
});
}]);
view raw gistfile1.txt hosted with ❤ by GitHub

Can be rewritten into:

.controller('MyController', ['SomeOtherVariableService', '$scope', '$stateParams', function(SomeOtherVariableService, $scope, $stateParams){
$scope.someOtherVariables = SomeOtherVariableService.getSomeVariableList(
$stateParams.someOtherParameter,
$stateParams.someEvenOtherParameter
);
}]);
view raw gistfile1.js hosted with ❤ by GitHub

The additional benefit now that we have is you have a named function for getting the someOtherVariable, and you can use it in other controllers/services without having to repeat code. Also, the unit test for the controller becomes very easy in the second case.

A sample implementation of the service can be:

.factory('SomeOtherVariableService', ['Restangular', function SomeOtherVariableService (Restangular){
return {
getSomeOtherVariable: function(someParameter, someOtherParameter){
return Restagular.one('someVariable').get({
someParameter: someParameter,
someOtherParameter: someOtherParameter
});
}
};
}]);
view raw gistfile1.js hosted with ❤ by GitHub

But what if we want to make a Restangular call in our service and ‘ehnance’ it additionally. Suppose a Restangular call returns :

{
someProp: 'someValue',
someOtherProp: 'someOtherValue'
}
view raw gistfile1.js hosted with ❤ by GitHub

And we would like our service to return:

{
someProp: 'someValue',
someOtherProp: 'someOtherValue',
//newly created prop
newlyCreatedProp: 'newlyCreatedProp'
}
view raw gistfile1.js hosted with ❤ by GitHub

If you wanted to achieve that directly in a controller, you would do something like that:

Restagular.one('someVariable').get({someParameter: someParameter})
.then(function(result){
var newRes = result;
newRes.newlyCreatedProp = 'newlyCreatedProp';
$scope.newRes = newRes;
});
view raw gistfile1.js hosted with ❤ by GitHub

However, if you were using a service, you would expect to have something like that:

// in controller, just call the service and expect it to have gotten that object/promise for you
$scope.newRes = SomeService.getNewRes(someParameter);
view raw gistfile1.js hosted with ❤ by GitHub

This, while it appears trivial, is slightly more tricky due to nature of promises. A sample implementation can be:

.factory('SomeVariableService', ['Restangular', '$q', function SomeVariableService (Restangular, $q){
return {
getNewRes: function(someParameter){
var newResDeferred = $q.defer();
Restagular.one('someVariable').get({someParameter: someParameter})
.then(function(result){
var newRes = result;
newRes.newlyCreatedProp = 'newlyCreatedProp';
newResDeferred.resolve(newRes);
});
return newResDeferred.promise;
}
};
}]);
view raw gistfile1.js hosted with ❤ by GitHub

And since this is also a bit hard to test, below is a sample jasmine unit test:

// sample jasmine test
describe('Test', function(){
// the variables that we need in each unit test. TestUtils is a service containing common test methods
var httpBackend, Restangular, TestUtils, q, scope;
// first we initialize the required modules
beforeEach(function() {
// the module to be tested
module('someModuleContainingTheServiceToBeTested');
// the test utils module
module('someModuleContainingTestUtils');
});
// then we use the $injector to obtain the instances of the services we would like to mock/use
// but not of the service that we want to test
beforeEach(inject(function( _Restangular_, _$httpBackend_, _TestUtils_, $q, $rootScope) {
httpBackend = _$httpBackend_;
Restangular = _Restangular_;
TestUtils = _TestUtils_;
q = $q;
scope = $rootScope.$new();
}));
// a sample definition on which method we are about to test
describe('getNewRes test', function(){
// actual test implementation
it('A description of what should the method do', inject(function(SomeVariableService){
// set up a spy on Restangular, so we test with what parameters it was called, also allow the call to continue
spyOn(Restangular, 'one').andCallThrough();
// a mock to be returned from http. We would later expect our service to 'enhance' this mock with an additional property
var mockToReturn = {
someProp: 'someValue',
someOtherProp: 'someOtherValue'
};
// a parameter with which the http service we expect to be called
var someParameter = 'someParameter';
// httpBackend would append a "/" in front of a restangular call
httpBackend.expectGET('/someVariable', {
someParameter: someParameter
})
// respond with the mock
.respond(mockToReturn);
// now call our service
var newRes = SomeVariableService.getNewRes(someParameter);
// handle restangular expectations
expect(Restangular.one).toHaveBeenCalledWith('someVariable');
// flush the backend to unproxy the restangular promise
httpBackend.flush();
// now follows the tricky part. The restangular promise has been unproxied by the httpBackend.flush call,
// but our promise, the one we return in the service, still hasn't been unproxied
// so, if we were to directly expect it to be unproxied, we are in for a surprise, it is a still a promise
// this took some fiddling, but I created a utility function that will do the unproxying for you:
newRes = TestUtils.resolvePromise(newRes, q, scope);
// expect the new object to have been 'enhanced' by the service
expect(newRes).toEqual({
someProp: 'someValue',
someOtherProp: 'someOtherValue',
newlyCreatedProp : 'newlyCreatedProp'
});
}));
});
});
view raw gistfile1.js hosted with ❤ by GitHub

And the TestUtils’ method:

resolvePromise: function (promise, q, scope){
var defer = q.defer();
var unproxiedPromise;
promise.then(function(value){
unproxiedPromise = value;
});
defer.resolve();
scope.$apply();
return unproxiedPromise;
}
view raw gistfile1.js hosted with ❤ by GitHub

Edit:

It is worth to note that Angular devs are deprecating the automatic resolve of promises:

http://stackoverflow.com/questions/19472017/angularjs-promise-not-binding-to-template-in-1-2

So your code would look a bit more cluttered but confusion would be avoided.

This plunkr shows that in angular 1.2rc3 the first method works:
http://plnkr.co/edit/SU5UMK7jNffXWnWiV1HE?p=preview

But if you were to try to take advantage of the automatic unrwapping of promises (which is deprecated in rc3, you are out of luck):
http://plnkr.co/edit/zdepkLCesYkj8pXYFTN2?p=preview

4 thoughts on “Restangular usage in a service +examples and unit tests

  1. i have a problem with the function resolvePromise wich is a service of a certain module
    is give me an error saying it’s a then object ? have any clue ?

    1. Well you can resolve your promises automatically in your controller, without having to unproxy them. If you look at this Plunkr: http://plnkr.co/edit/SU5UMK7jNffXWnWiV1HE?p=info

      This is the recommended way to resolve a promise in your controller now: using the construct

      SomeService.doSomethingThatIsAPromise.then(function(result){
      $scope.myVariable = result;
      });

      You wouldn’t have to do unproxying of your promises. Look at this example as well:

      http://stackoverflow.com/questions/20982461/using-http-and-q-to-fetch-data/20982497#20982497

      If you do that, you would have to mock your service in your test to return a promise. This would be simpler than playing with the low level $http expect thingies. If you still have trouble, give me a plunkr of something and I’d try to help you.

      Cheers.

  2. Restangular offers a extendModel function. That way you can easily extend a model served by Restangular and dont have to do it each time manually in your service.

    For the other parts: I fully agree with the service approach and not using Restangular directly in your controller. That way you clearly separate concerns and can even switch out Restangular for another library without changes in your controllers.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.