AngularJS: testing services that use $resource

In this article we discuss one aspect of AngularJS testing, namely how to unit test code that uses $resource.

Currently I’m working on a project that uses ngResource (imported into the code as $resource) and the unit tests related to the service relying on $resource were failing. I jumped in to do a rewrite or to scrap them entirely if necessary.

My tool kit:

  • Karma test runner
  • Jasmine unit testing/spying/mocking framework

The service looked something like this:

APRIL 2017 UPDATE: Click here to see the code for testing AngularJS 1.6 services that use $resource.

The Problem

How to unit test a service that relies on $resource was the problem. When I searched around on how to test services or factories that rely on $resource, it appeared that people were trying to mock out $resource which didn’t work. Most of them also tried what we see above and mocked out $httpBackend. This partially works, but didn’t allow for the forcing of certain code paths which is necessary for getting maximum test coverage.

Unit Tests Vs. Integration Tests

AngularJS uses dependency injection to make it easier to test things like this, but once you’re mocking out objects like $httpBackend and $resource, you’re no longer writing a unit test. You’re instead writing an integration test.

An integration test works by testing the class and its integration with other dependencies. With $resource-based services, your unit test for the service is an integration test.

The Solution

After some days of searching StackOverflow and other tutorials and articles on how to unit test AngularJS services that use $resource, “Working Effectively with Legacy Code” gave me an answer in a few minutes of reading: move the functionality into a new class.

Diagram of the dependency on $resource being broken into two parts (the service and the wrapper that combines the service with $resource).

To break the dependency of the service on $resource, I used the advice of “Working Effectively with Legacy Code”.

This is a wonderful book that every software developer should have on their shelves or on their ebook reader. Great reference books are a god-send and quickly help you solve a problem.

The reason for this is that the callback functions given to the $resource promise are private functions. There is no way of accessing them until you call the promise and force it.

To avoid writing unit tests based on mocking and to avoid having inaccessible/untestable functions, I moved the callback functions to a new class and the OriginalResource now only contains methods that require $resource:

Testing angularjs services with spies

There is now a new service, Original, that doesn’t rely on $resource. The old service, OriginalResource, hooks up the new service to the $resource class. This is far easier to test because you can invoke the success or failure callbacks whenever you like and check to make sure it changes the right parts of the system.

For a quick tutorial on using $httpBackend, check out the article “$httpBackend used for faking a backend”.

The conclusion: when you’re writing services that interact with the backend, you may need to break your service into two services to avoid mock objects. And this is okay! There’s nothing wrong with refactoring code to make it testable without having to resort to mocks. “Working Effectively with Legacy Code” has a lot of other good advice for dealing with spaghetti code.

Click here for Angular 2 vs 1 complete list of differences

AngularJS 2 Directive Component Video Tutorial

If you want to be on the cutting-edge of web development, check out my video tutorial on creating directive components in Angular 2 using vanilla JavaScript.

Click here to find other articles on AngularJS and to subscribe to the Learning AngularJS newsletter

The full source code for the example in this article is in a Gist on Github:

6 Comments to AngularJS: testing services that use $resource

  1. Since $resource is part of Angular, I don’t see any reason to mock or avoid it. You can consider it an integration test, but that’s like saying $routeProvider or $q should be isolated (or an even more extreme case – jQuery/lodash/etc) in order to create a “real” unit test. At some point you have to rely on the framework you are using and understand it has its own unit tests to cover the functionality you are using.

    When I test against $resource, I simply include module(‘ngResource’) and spy on methods and/or mock $httpProvider. If I need to use my resource somewhere else I just mock it, and there is no need for including ngResource, eg:

    var User = jasmine.createSpyObj(‘User’, [‘get’]);
    User.get.and.returnValue({ $promise: $q.when({ id: 1, name: ‘Fred’ }) });

    • Rudolf Olah says:

      Wow, solid advice here!

      I agree and I think that’s what the $httpBackend-based tests were failing at; they’re too low-level and were basically testing $resource to some extent.

      However, moving the functions that rely on $resource into another service/factory has made it much much easier to see what’s happening. Getting closer to the single-responsibility principle for classes = a good thing.

  2. […] AngularJS: testing services that use $resource […]

  3. mariowise says:

    Hi! Very nice tut.. but what happens when we need to configure that $resource, with trailingSlashes or headers tokens? I’ve got this SO question. Thanks!

  4. […] noticed that the AngularJS article on testing with mocks is still very popular. Thanks everyone for reading it, if you use Ember.js I’ll try and put together an article on […]