Skip to content

Instantly share code, notes, and snippets.

@felippenardi
Created June 26, 2016 17:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felippenardi/16abf6d97afb501162f4a4b7000a6cb5 to your computer and use it in GitHub Desktop.
Save felippenardi/16abf6d97afb501162f4a4b7000a6cb5 to your computer and use it in GitHub Desktop.
# Testing an Angular $resource factory
#angular #medium
Learn from live example how to test a $resource factory. What to test? What /not/ to test? When to use $resource? These questions will be answered here.
[DEMO LINK](https://gist.run/?id=38949509eed11e6c1527218385579f80)
Let's start with a brief review.
## What is $resource and when to use it?
The `$resource` is an Angular Service that makes it really easy to setup [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) operations with [RESTful APIs](http://stackoverflow.com/questions/671118/what-exactly-is-restful-programming). If you have a */api/posts/* , this is all the code you need to setup http operations for *POST*, *PUT*, *DELETE* and *GET*:
``` javascript
// Suppose you have "$resource" injected
var url = "/api/posts/:id";
var params = { id: "@id" };
var Posts = $resource(url, params);
```
With these three lines `Posts` , this is all you get out of the box:
``` javascript
Posts.query(); // GET /api/posts
Posts.get({id:1}); // GET /api/posts/1
Posts.save(/*...*/); // POST /api/posts/
Posts.remove({id:1}); // DELETE /api/posts/1
Posts.delete({id:1}); // DELETE /api/posts/1
```
Working with a RESTful API gives you the advantage to interact with the backend in previsible way. By using *$resource* you are taking advantage of it.
You can also setup custom actions by giving a third paramter to the *$resource* call:
``` javascript
// Suppose you have "$resource" injected
var url = "/api/posts/:id";
var params = { id: "@id" };
var actions = { update: { method: 'PUT' } }; // NEW LINE
var Posts = $resource(url, params, actions); // Passing "actions" as the third parameter
```
In this example `Posts` will still have all previous default methods ( `.get`, `.query`, `.update`, `.delete` and `.remove` ) plus the new `.update` :
``` javascript
Posts.update({id:1}); // PUT /api/posts/1
```
Besides that, you can perform transformations on the data received or even before it is sent, you can cancel an on going request, specify different URLs, cache responses, and much more. [The Angular's official $resource DOCs](https://docs.angularjs.org/api/ngResource/service/$resource) does a great job guiding you in all these details.
## How to test $resource instance?
Our focus here is test the $resource instance itself, as a factory. In future article I'll explain how to test its use in a controller, directive or service.
### Test setup
Before we are ready to start writting tests, we'll be writting our test setup. We'll be interacting with http so we need something to intercept it in our tests. Convinently, *$resources* uses *$http* in its implementation, so we can inject *$httpBackend* in our test. Next, we'll need to inject the service we are testing, so that we can interact with it and assert behavior expectations. So this is what our test setup looks like:
``` javascript
describe('Service: User', function() {
var User, _inject, _setup, httpBackend;
User = null;
httpBackend = null;
_inject = function() {
inject(function(_User_, $httpBackend) {
User = _User_;
httpBackend = $httpBackend;
});
};
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
beforeEach(function() {
module('demo.module');
});
describe('the User resource', function() {
beforeEach(function() {
_inject();
});
//...continue below
```
You'll notice a *_inject* function there, that is invoked in the begnning of every test. For this specifc example, injecting *$httpBackend* and *User* (the service we are going to test) just once would be enough, but using this pattern is often helpful when getting into more complex tests. You can [ read more about that pattern](https://github.com/daniellmb/angular-test-patterns/blob/master/patterns/service.md#unit-testing-angularjs-services) on the [Angular Test Patterns](https://github.com/daniellmb/angular-test-patterns) repository.
### What to test?
So let's write our first test: *does the service exist?*
``` javascript
it('exists', function() {
expect(!!User).toBe(true);
});
```
This will fail until you create a simple service structure with that name.
*Does it returns a list of users?*
```
it('returns a list of users', function() {
// Arrange
var users;
httpBackend.expectGET('/api/users').respond([{}, {}, {}]);
// Act
users = User.query();
httpBackend.flush();
// Assert
expect(users.length).toBe(3);
});
```
Note that we are separating the test blocks in groups of "Arrange", "Act" and "Assert", so that we make sure only one thing is being tested per time. This convention brings clarity to the test and significatively reduces our chance of writing complicate or unecessary tests.
Two more things to note here:
* We did not injected *$resource* to our tests
* We are teseting the *.get()* method that comes out of the box with the resource.
The reason we didn't inject *$resource* is because that as long as this test is making the right http interaction when I call the *.get()* method, it doesn't matter what kind of implementation I'm using. That is also the reason why we are testing the *get()* method: we need to make sure it will be available for us to use.
The benefit of not injecting *$resource* and mocking it in our tests is that we can change the implementation later without needing to change our tests. A great example of when replacing *$resource* is a good idea is [Angular Cached Resource](https://github.com/goodeggs/angular-cached-resource) : a wrapper around *$resource* that gives you caching capabilities to built offline Angular applications. If you later decide to use it, your tests will mostly need no changes. I'll write about how to convert an app from *$resource* to Angular Cached Resource in the future.
--
The example I wrote for you goes even further on how to test:
* Fetching the collection
* Fetching a single item
* Custom *$resource* actions with custom urls
* Transforming the data returned by the backend
* Adding a custom property to the resource instance
* And more test examples you can use for your own tests
[DEMO LINK](https://gist.run/?id=38949509eed11e6c1527218385579f80)
### What not to do
* Do not inject *$resource* or mock its implementation. Test that the HTTP interactions happens as expected, and not how the service is implemented.
* When intercepting the response of a custom method, do not transform the response string into json yourself. When you use the *transformResponse* action property of a *$resource*, you replace the default angular parser that is transforming backend response from string to JSON. It has some logic to ensure that the response is a JSON and can be parsed securely. You can use it by injecting *$http* into your factory and using *$http.defaults.transformResponse*.
## Conclusion
While the article focused on what the test part of the code looks like (which is the first thing we do when TDDing), you can see how to code the resource service on the [live Gist Example](https://gist.run/?id=38949509eed11e6c1527218385579f80) .
Remember that the single purpose of a resource is interacting with the API. Do not add custom methods to it that does not interact with the API. If you need to offer some data transformation before or after it comes from the backend, make sure to embed this in a action that is interacting with the API.
If there is something you need more information on how to test, let me know and I'll be glad to help you build Angular apps with TDD.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment