About Emil Galen

My name is Emil van Galen, I work for JDriven. I'm passionate about software development and continuously seeking for ways to improve code, learn new technologies, improving software design and keeping things fresh.

ngImprovedTesting: mock testing for AngularJS made easy

Being able to easily test your application is one of the most powerful features that AngularJS offers. All the services, controllers, filters even directives you develop can be fully (unit) tested.

However the learning curve for writing (proper) unit tests tends to be quite steep.
This is mainly because AngularJS doesn’t really offer any high level API’s to ease the unit testing. Instead you are forced to use the same (low level) services that AngularJS uses internally. That means you have to gain in dept knowledge about the internals of $controller, when to $digest and how to use $provide in order to mock these services. Especially mocking out a dependency of controller, filter or another service is too cumbersome.

This blog will show how you would normally create mocks in AngularJS, why its troublesome and finally introduces the new ngImprovedTesting library that makes mock testing much easier.

Sample application

Consider the following application consisting of the “userService” and the “permissionService”:

var appModule = angular.module('myApp', []);
 
appModule.factory('userService', function($http) {
    var detailsPerUsername = {};
 
    $http({method: 'GET', url: '/users'})
        .success(function(users) {
            detailsPerUsername = _.indexBy(users, 'username');
        });
 
    return {
        getUserDetails: function(userName) {
            return detailsPerUsername[userName];
        }
    };
});
 
appModule.factory('permissionService', function(users) {
    return {
        hasAdminAccess: function(username) {
            return users.getUserDetails(username).admin === true;
        }
    };
});

When it comes to unit testing “permissionService” there are two default strategies:

  • using mock $httpBackend (from the ngMock module) to simulate $http trafic from the “userService”
  • using a mock instead of the actual “userService” dependency

Replacing the “userService” with a mock using vanilla AngularJS

Using vanilla AngularJS you have to do all the hard work yourself when you like to create a mock. You will have to manually create an object with its relevant fields and methods. Finally you will have to register the mock (using $provide) to overwrite the existing service implementation.

Using the following vanilla AngularJS we can replace “userService” with a mock in our unit tests:

describe('Vanilla mocked style permissions service specification',
        function() {
    var userServiceMock;
 
    beforeEach(module('myApp', function ($provide) {
        userServiceMock = {
            getUserDetails: jasmine.createSpy()
        };
 
        $provide.value('userService', userServiceMock);
    }));
 
    // ...

The imperfections of the vanilla style of mocking

To ability to mock services in unit tests is a really great feature in AngularJS but it’s far from perfect.

As a developer I really don’t want to be bothered with having to manually create a mock object. For instance I might just simply forget to mock the “userService” dependency when testing the “permissionService” meaning I would accidentally test it using the actual “userService”. And what if you would refactor the “userService” and would rename its method to “getUserInfo”.
Then you would except the unit test of “permissionService” to fail, right? But it won’t since the mocked “userService” still has the old “getUserDetails” (spy) method.

Make things even worse… what if you would rename service to “userInfoService”. This makes the “userService” dependency of the “permissionService” to be no longer resolvable. Due to this modification the application will no longer bootstrap when executed inside a browser. But when executed from the unit test it won’t fail since its still uses its own mock. However other unit tests using the same module but not mocking the service will fail.

How mock testing could be improved

Coming from a Java background if found the manual creation of mocks felt quite weird to me. In static languages the existence of interfaces (and classes) make it way more easy to automatically create mocks.

Using AngularJS we could do something similar …
… what if we would use the original service as a template for creating a mocked version.

Then we could automatically create mocks that contain the same properties as the original object. Each non-method property could be copied as-is and each method would instead be a Jasmine spy.

Instead of manually registering a mock service using $provide we could instead automate this. This would also allow us to automatically check if a service you want to mock actually exists. Also we could check if the service being mock is indeed being used as dependency of a component.

Introducing the ngImprovedTesting library

With the intention of making (unit) testing more easy I created the “ngImprovedTesting” library. The just released 0.1 version supports (selectively) mocking out dependencies of a controller, filter or another service.

Mock out the “userService” dependency when testing the “permissionService” is now extremely easy:

describe('ngImprovedTesting mocked style permissions service specification',
        function() {
 
    beforeEach(ModuleBuilder.forModule('myApp')
        .serviceWithMocksFor('permissionService', 'userService')
        .build());
 
    // ... continous in next code snippets

Instead of using the traditional “beforeEach(module(‘myApp’))” we are using the ModuleBuilder of “ngImprovedTesting” to build a module specifically for our test. In this case we would like to test the actual “permissionService” in a test in combination with a mock for its “userService” dependency.

But what if I would like to set some behavior on the automatically created mock …
… how do I actually get a hold on the actual mock instance?

Well simple… besides the component being tested all its dependencies including the mocked one can be injected.

To differentiate a mock from a regular one it’s registered with “Mock” appended in its name. So to inject the mocked out version of “userService” just use “userServiceMock” instead:

  describe('hasAdminAccess method', function() {
        it('should return true when user details has property: admin == true',
                inject(function(permissions, userServiceMock) {
            userServiceMock.getUserDetails.andReturn({admin: true});
 
            expect(permissions.hasAdminAccess('anAdminUser')).toBe(true);
        }));
    });

As you can see in the example the “userServiceMock.getUserDetails” method is a just a Jasmine spy. It therefor allows invocation of “andReturn” on in order to set the return value of the method. However it does not allow an “andCallThrough” as the spy is not on the original service.

Exploring the ModuleBuilder API of ngImprovedTesting

Since I didn’t get round to writing and generating JSDocs / NGDocs, I instead will quickly explain it here.

To instantiate a “ModuleBuilder” use its static “forModule” method.
The “ModuleBuilder” (in version 0.1) consists of the following instance methods:

  • serviceWithMocksFor: registers a service for testing and mock specified dependencies
  • serviceWithMocks: registers a service for testing and mock all dependencies
  • serviceWithMocksExcept: registers a service for testing and mock dependencies except the specified
  • controllerWithMocksFor: registers a controller for testing and mock specified dependencies
  • controllerWithMocks: registers a controller for testing and mock all dependencies
  • controllerWithMocksExcept: registers a controller for testing and mock dependencies except the specified
  • controllerAsIs: registers a controller so that it can be instantiated through $controller
  • filterWithMocksFor: registers a filter for testing and mock specified dependencies
  • filterWithMocks: registers a filter for testing and mock all dependencies
  • filterWithMocksExcept: registers a filter for testing and mock dependencies except the specified
  • filterAsIs: registers a filter so that is can be using through $filter

Limitations in the initial (0.1) of ngImprovedTesting

Although version 0.1 is quite production ready (and well unit tested) is has its limitations:

  • Services registered with the “provider” method currently cannot be used as to be tested service; meaning it cannot be used as first parameter of “serviceWithMocks…”, however it can be used as a (potentially mocked) dependency.
  • Services which are registered using “$provide” (i.e. inside a config function of a module) instead of through “angular.Module” cannot be used as to be tested service.
  • Mock testing of directives is currently not supported.

How to get started with ngImprovedTesting

All sources from this blog post can be found as part of a sample application:

The sample applications demonstrates three different flavors of testing:

  • One that uses the $httpBackend
  • Another using vanilla mocking support
  • And one using ngImprovedTesting

To execute the tests on the command-line use the following commands (requires NodeJS, NPM, Bower and Grunt to be installed):

npm install
bower update
grunt

The actual sources of ngImprovedTesting itself are also hosted on GitHub:

Furthermore ngImprovedTesting is also available through bower itself. You can easily install and add it to an existing project using the following command:

bower install ng-improved-testing --save-dev

 

Your feedback is more than welcome

My goal for ngImprovedTesting is to ease mock testing in your AngularJS unit tests.
I’m very interested in your feedback… is ngImprovedTesting any useful… and how could it be improved?

Reference: ngImprovedTesting: mock testing for AngularJS made easy from our JCG partner Emil van Galen at the JDriven blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


two + = 5



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close