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:
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.
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.
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:
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.
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.
AngularJS 2 Directive Component Video Tutorial