Mocking Services for AngularJS tests

One of the things that I’ve enjoyed more in the last year is Test Driven Development, with the satisfaction of seeing the green after all tests have passed. AngularJS is written with testability in mind which makes testing applications easy. And although it can be hard to grasp at the beginning, there are a lot of good resources around to learn how to unit test the application logic.

But in this post I want to focus in writing mock objects. In previous versions of the AngularJS generators for Yeoman there was a mock folder generated inside of the test directory. This made me think on a way to write separated files for every necessary mock so we can include them where required. With the new structure recommendations this mock folder has been removed, but the point of writing separate files it’s the same.

Let’s say we have a complex Auth service to deal with user authentication.

angular.module('webApp')
  .factory('Auth', function($http) {
    var currentUser = {};
    return {
      login: function(credentials) {
        $http.post('/api/auth', credentials).then(function(response) {
          currentUser = response.data.user;
        });
      },
      logout: function() {
        currentUser = {};
      },
      isLoggedIn: function() {
        return !!currentUser.id;
      }
    };
  });

This service could be injected in a controller to submit a form just if the user is logged in.

angular.module('webApp')
  .controller('ContactCtrl', function(Auth) {
    this.showAlert = false;
    this.submitted = false;
    this.submitForm = function() {
      // If the user is not logged-in, return and display the alert.
      if (!Auth.isLoggedIn()) {
        this.showAlert = true;
        return;
      }
      // Handle form submit.
      this.submitted = true;
    };
  });
<div ng-controller="ContactCtrl as contact">
    <form name="contactForm" ng-submit="contact.submitForm()">
        <button type="submit">Submit</button>
        <div class="alert" ng-if="contact.showAlert">
            Please <a href="/login">login</a>
        </div>
    </form>
</div>

Note that for the sake of example, the code of the service has been reduced to a minimum while the form controller it’s pretty dumb; this way we can just get the idea of the functionality and focus in writing the mock object.

How to mock our Service?

Another long topic in AngularJS is the difference among Factory, Service, Value, Constant and Provider. But for creating the mock we just need to know that these five types are built on top of Provider, so the injector is going to look for an AuthProvider.

To write our Provider we don’t need to use $http to interact with the backend, as we just need to emulate the functionality.

angular.module('authMock', [])
  .provider('Auth', function() {
    this.userLoggedIn = false;
    this.$get = function() {
      return  {
        login: function() {
          this.userLoggedIn = true;
        },
        logout: function() {
          this.userLoggedIn = false;
        },
        isLoggedIn: function() {
          return this.userLoggedIn;
        }
      };
    };
  });

So we can unit test our Controller injecting the mocked Service.

describe('Controller: ContactCtrl', function() {

  // Load the controller module.
  beforeEach(module('webApp'));
  // Load the mock service module.
  beforeEach(module('authMock'));

  var ContactCtrl, Auth;
  // Initialize the controller and the mocked service.
  beforeEach(inject(function($controller, _Auth_) {
    Auth = _Auth_;
    ContactCtrl = $controller('ContactCtrl', {
      Auth: Auth
    });
  }));

  it('should submit the form if the user is logged in', function() {
    Auth.login();
    ContactCtrl.submitForm();
    expect(ContactCtrl.submitted).toBe(true);
  });

  it('should not submit the form if the user is not logged in', function() {
    Auth.logout();
    ContactCtrl.submitForm();
    expect(ContactCtrl.submitted).toBe(false);
  });

});

I have put all this code together on a JSFiddle so you can see the specs passing.

Advertisements

2 thoughts on “Mocking Services for AngularJS tests

  1. Thank you so much for this post. I have just completed the Thinkster.io tutorial and was trying to do unit testing with Firebase , tearing my hair out in the process. Now, after your post, I can see that I need to mock the Firebase calls.
    I have really struggled to find any articles at a conceptual level about this.
    Many thanks.
    Alan

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s