In this article we discuss one aspect of AngularJS testing, namely how to unit test code that uses $resource.
When working on a project that uses ngResource (imported into the code as $resource), 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.
- The Problem: how to test a service that relies on $resource
- Unit Tests Vs. Integration Tests
- The Solution: break the dependency and test each part separately
My tool kit for angularjs testing:
- 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.
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 did not allow for the forcing of certain code paths which is necessary for getting maximum test coverage.
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.
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.
Working Effectively with Legacy Code: Break the Dependency in the Code
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.
The Importance of Reference Books!
Great reference books are a god-send and quickly help you solve a problem; they can help you clear your mind and give you a new perspective on a problem. I’ve found that to be the case many times with Working Effectively With Legacy Code, especially its key idea that you must break dependencies.
Implementing the Fix
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. This is a common problem, not only in angularjs testing but in other languages (such as PHP) and frameworks (such as Django) as well.
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.
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.
Let me go over those steps again:
- Notice that it is hard to test OriginalService which uses $resource directly
- Create a new class, OriginalResourceService that depends on $resource
- Move code that relies on $resource from OriginalService into OriginalResourceService (this is known as a sprout class, a way of breaking dependencies)
- You can now write a unit test for OriginalService that ensures it delegates to OriginalResourceService and that handles both the “happy path” and the error cases such as special exceptions or error codes
- You can now write a unit test for OriginalResourceService with $resource mocks to handle the happy path and error cases such as $resource being returned a 404 error
Conlusion: Break Dependencies with Single-Responsibility Classes
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.
The full source code for the example in this angularjs testing article is in a Gist on Github:
If you’re an Emacs user, be sure to check out the angularjs-mode for Emacs.
Check out some books on AngularJS.