Skip to content

Instantly share code, notes, and snippets.

@felippenardi
Last active June 26, 2016 17:28
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/2df0619b1de00807146caeaea328f468 to your computer and use it in GitHub Desktop.
Save felippenardi/2df0619b1de00807146caeaea328f468 to your computer and use it in GitHub Desktop.

Testing an Angular $resource factory

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

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 operations with RESTful APIs. If you have a /api/posts/ , this is all the code you need to setup http operations for POST, PUT, DELETE and GET:

// 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:

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:

// 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 :

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 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:

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 on the Angular Test Patterns repository.

What to test?

So let's write our first test: does the service exist?

    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 : 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

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 .

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