Unit testing in AngularJS - Mocking Services and Promises

In Angular everything seems to have a steep learning curve and unit testing an Angular app definitely doesn't escape this paradigm.

When I started with TDD and Angular I felt that I was spending twice (maybe more) as much time figuring out just how to test and maybe even more just getting my tests set up correctly. But as Ben Nadel put it in his blog there are ups and downs in the angular learning process - his graph is definitely my experience with Angular.

However as I have progressed in learning Angular and unit testing as well, now I feel that I am spending much less time setting up tests and much more time making tests go from red to green - which is a good feeling.

I have come across different methods of setting up my unit test to mock services and promises.

So onto the code I am sure you don't wanna listen to some guy blab about his love, err accomplishments learning a framework, especially when his blog is about JS and beer - how drunk could he be?

This is how I started out mocking my services and promises, I'll use a controller, but services and promises can be mocked in other places using the same technique.

describe('Controller: Products', function () {
        var//iable declarations
            $scope,
            $rootScope,
            ProductsMock = {
                getProducts: function () {
                } // There might be other methods as well but I'll stick to one for the sake of conciseness
            },
            PRODUCTS = [{},{},{}]
        ;
    
        beforeEach(function () {
            module('App.Controllers.Products');
        });
    
        beforeEach(inject(function ($controller, _$rootScope_) {
            //Set up our mocked promise
            var promise = { then: jasmine.createSpy() };
    
            //Set up our scope
            $rootScope = _$rootScope_;
            $scope = $rootScope.$new();
    
            //Set up our spies
            spyOn(ProductsMock, 'getProducts').andReturn(promise);
    
            //Initialize the controller
            $controller('ProductsController', {
                $scope: $scope,
                Products: ProductsMock
            });
    
            //Resolve the promise
            promise.then.mostRecentCall.args[0](PRODUCTS);
    
        }));
    
        describe('Controller Initialization', function () {
            it('should have a populated array of products', function () {
                expect('ProductsMock.getProducts').toHaveBeenCalled();
            });
        });
    });

This worked, but as time went on I thought there must be a better way. For one, I hated the

promise.then.mostRecentCall

thing, and if I wanted to reinitialise the controller then I had to pull it out of the beforeEach block, and inject it individually into each test, making our tests definitely not DRY.

There has to be a better way...

Then I came across another post, blog, stackoverflow example (you pick it I was probably there), and I saw the use of the $q library.

D'oh!

Why set up a whole mock promise when we can just use the tool that Angular gives us. Our code looks cleaner and it's much more intuitive - no ugly promise.then.mostRecent thing. Then to DRY everything out, wrap the controller instantiation in a function so we can have more control over how the controller behaves for setting up our different test conditions through the use of parameters and CONSTANTS. Now we're getting sommewhere.

Next in the iteration of unit testing was this.

describe('Controller: Products', function () {
        var//iable declarations
            $scope,
            $rootScope,
            $q,
            $controller,
            products,
            PROMISE = {
                resolve: true,
                reject: false
            },
            PRODUCTS = [{},{},{}] //constant for the products that are returned by the service
        ;
    
        beforeEach(function () {
            module('App.Controllers.Products');
            module('App.Services.Products');
        });
    
    
        beforeEach(inject(function (_$controller_, _$rootScope_, _$q_, _products_) {
            $rootScope = _$rootScope_;
            $q = _$q_;
            $controller = _$controller_;
            products = _products_;
            $scope = $rootScope.$new();
        }));
    
        function setupController(product, resolve) {
            //Need a function so we can setup different instances of the controller
            var getProductsPromise = $q.defer();
    
            //Set up our spies
            spyOn(products, 'getProducts').andReturn(getProductsPromise.promise);
    
            //Initialise the controller
            $controller('ProductsController', {
                $scope: $scope,
                products: products
            });
    
            // Use $scope.$apply() to get the promise to resolve on nextTick().
            // Angular only resolves promises following a digest cycle,
            // so we manually fire one off to get the promise to resolve, or in this
            // case we can choose through the parameters being passed in whether or
            // not to resolve the promise - thus covering our test cases. 
            if(resolve) {
                $scope.$apply(function() {
                    getProductsPromise.resolve(product);
                });
            } else {
                $scope.$apply(function() {
                    getProductsPromise.reject();
                });
            }
        }
    
        describe('Resolving and Rejecting the Promise', function () {
            it('should return the first PRODUCT when the promise is resolved', function () {
                setupController(PRODUCTS[0], PROMISE.resolve); // Set up our controller to return the first product and resolve the promise. 
                expect('to return the first PRODUCT when the promise is resolved');
            });
    
            it('should return nothing when the promise is rejected', function () {
                setupController(PRODUCTS[0], PROMISE.reject); // Set up our controller to return first product, but not to resolve the promise. 
                expect('to return nothing when the promise is rejected');
            });
        });
    });

This started to feel like the way it should be set up. We can mock what we need to mock, we can set our promise to resolve and reject. We can truly test the two possible outcomes. This feels good.

Ui-Router State Transition Animations & Bootstrap

I was given what I thought was a nearly impossible task by my product owner - create an animation when changing states. I began using ngAnimate and came up with what I thought was a pretty cool solution to his problem - wrong.

"This isn't what I had in mind he told me. As I change between panels of a collapse widget I want to change the state and update the URL as well."

Me - "Oh boy..."

So how else can I animate state transitions using an accordion like approach or in bootstrap terminology - the collapse widget?

Back to the drawing board...

I came up with what I thought was a really cool way to animate transitions using ui-router and bootstrap.

First off lets set up the states that we need transitioning. I will only show this with two collapse panels, but in theory there can be as many as needed.

app.js

    .state('home.checkout', {
            url: 'checkout',
            views: {
                '@home': {
                    templateUrl: 'views/partials/generic/checkout-process/order-checkout-root.html'
                }
            }
        })

        .state('home.checkout.shoppingcart', {
            url: '^/shoppingcart',
            views: {
                'shopping-cart@home.checkout': {
                    templateUrl: 'views/partials/generic/checkout-process/shoppingcart/shopping-cart-partial.html',
                    controller: 'ShoppingCartController'
                },
                'order-confirmation@home.checkout' : {
                    templateUrl: 'views/partials/generic/checkout-process/closed-state.html',
                    controller: function($scope) {
                        $scope.page = {name: 'Order Confirmation'};
                        $scope.state = {name: 'home.checkout.confirm'};
                    }
                }
            }
        })

        .state('home.checkout.confirm', {
            url: '/confirmation',
            views: {
                'shopping-cart@home.checkout': {
                    templateUrl: 'views/partials/generic/checkout-process/closed-state.html',
                    controller: function($scope) {
                        $scope.page = {name: 'Shopping Cart'};
                        $scope.state = {name: 'home.checkout.shoppingcart'};
                    }
                },
                'order-confirmation@home.checkout': {
                    templateUrl: 'views/partials/generic/checkout-process/confirmation/order-confirmation-partial.html',
                    controller: 'OrderConfirmationController'
                }
            }
        })

HTML

order-checkout-root.html

    <div class="row checkout-process">
        <section class="col-sm-8 col-md-8 col-lg-8 panel-group" id="accordion">
            <div class="shopping-cart panel panel-default" ui-view="shopping-cart" autoscroll="false"></div>
            <div class="order-confirmation panel panel-default" ui-view="order-confirmation" autoscroll="false"></div>
        </section>
    </div>

closed-state.html

    <article class="col-sm-12 col-md-12 col-lg-12 panel-heading closed-state">
        <h4 class="panel-title">
            <a ui-sref="{{state.name}}">
                {{page.name}}
            </a>
        </h4>
    </article>

order-confirmation-partial.html

I will only include this one and not the other partial as its the same idea.

    <div class="order-confirmation-page row">
        <div class="panel-heading">
            <h4 class="panel-title">Order Confirmation</h4>
        </div>

        <div class="panel-collapse collapse" pttp-collapse-toggler data-toggle="collapse">
            <div class="panel-body">
                <!--Code for the collapse body goes here-->
            </div>
        </div>
    </div>

Whats important from this last partial is to note the inclusion of the directive

pttp-collapse-toggler

This is where we do our work and the most interesting part of the code

collapseTogglerDirective.js

    'use strict';

    angular.module('PTTP.Directives.CollapseToggler', [])

        .directive('pttpCollapseToggler', function ($rootScope, $state, $q, $timeout) {

            var linker = function(scope, element) {

                var
                    collapse = $q.defer(),
                    changeEventStarted = false
                ;

                //Expand the panel on directive instantiation
                $timeout(function() {
                    $(element).collapse('show');
                }, 300);


                scope.$on('$stateChangeStart', function(event, toState) {
                    //Check to make sure we arent in the middle of a $stateChangeEvent
                    if(changeEventStarted) {
                        return;
                    }
                    //Stop the state transition
                    event.preventDefault();

                    //Collapse the panel
                    $(element).collapse('hide');

                    //Wait for the panel to collapse completely
                    collapse.promise.then(function() {
                        changeEventStarted = true;
                        //Then transiton the state
                        $state.transitionTo(toState);
                    });
                });

                //Event listener for the collapse completed
                $(element).on('hidden.bs.collapse', function() {
                    collapse.resolve();
                });
            };

            return {
                restrict: 'A',
                link: linker
            };
        });

Summary

In short what we do here is:

  1. Setup a promise to know when we can transition again.
  2. Intercept the $stateChangeStart event and stop it from happening.
  3. Then we collapse the panel we are interested in
  4. When the collapse is finished bootstrap fires an event saying I am done collapsing which we listen for and in turn resolve the promise
  5. When the promise is resolved we can safely transition to the next state

I hope that this isn't too much to follow, but if you do the potential it has for other kinds of animation is pretty sweet.

Building Reusable Bower Components from Angular Modules

So you have built a generic module and now you wanna use it in another project, but like any good developer out there you hate the idea of copying the code and pasting it into your new project.

Welcome to the world of the bower component.

For any developer out there who has been indoctrinated into the bower components knows their efficiency in making a working environment's dependencies easily shareable. But what if we wanna make our own component, well its actually super simple and through the use of bower link we can even edit our component in the project that we are using it in.

There is a great post on it here. Expect this article to be a fork of that post.

Benefits of Modular Components

  • Codebase in much DRYer - hopefully
  • Smaller pieces === More maintainable
  • Reduced coupling between modules

The most important benefit we gain from making these modules is reduced coupling.

The second point directly relates to the reduction of coupling as well for as a project grows in size and complexity, functionality is added and coupling issues no doubt creep into the scope of things. On the other hand when we have a small module we are able to see what the module depends on so we can keep it's dependencies small and when things grow to be too large or the complexity is too great we can more easily refactor the code into smaller modules.

This allows us to build lego blocks of code that we can add and remove as needed without breaking a project.

Example Module

For this example we will be building a product module, but this is just a generic choice I made based on what I am currently building at work.

In our product module we want to be able to fetch json, display the json to the user, plus maybe have some kind of interaction - the classic trifecta of angularJS' - service, controller, directive.

Project Layout

  • Service - PTTP.Service.Product - productService.js
  • Controller - PTTP.Controller.Product - productController.js
  • Directive - PTTP.Directive.Product - productDirective.js
  • Widget - PTTP.Widget.Product - productWidget.js

I won't get into the actual building of these pieces, but we will keep them as separate files with separate module names, and of course unit tests. The productWidget.js will be the concatenated version of the previous three files, plus the template which will be converted to JS - this will be the job of our old friend grunt.

Directory Structure

├── Gruntfile.js
├── README.md
├── bower.json
├── karma.conf.js
├── package.json
|
├── dist
│   └── productWidget.js
|
├── scripts
│   ├── productController.js
│   ├── productDirective.js
│   ├── productService.js
│   └── templates.js
|
├── test
│   ├── productCtrl.spec.js
│   ├── productDirective.spec.js
│   └── productService.spec.js
|
└── views
    └── templates
        └── product.tpl.html

This is a directory tree without having run bower install and npm install for easy of viewing.

bower.json

{
  "name": "pttp-angular-product",
  "version": "0.0.1",  
  "main": "./dist/productWidget.js", //<------------ dont forget me!
  "private": true,
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "angular": "~1.2.22",   
  },
  "devDependencies": {
    "angular-mocks": "~1.2.14",
    "angular-resource": "~1.2.22",
    "angular-cookies": "~1.2.22",
    "angular-sanitize": "~1.2.22",
  }
}

The main will tell bower that you need to use the widget as the file for any injection through grunt-wiredep, which if you aren't using well you should take the time and learn it. Also add any dependencies you might need here as well, this is where we have direct access to how coupled our module is to other bits and pieces.


Once you have your files written and are ready for making the productWidget.js - grunt time. We will be using grunt-contrib-concat to concatenate our JS, and grunt-html2js to convert our HTML to JS and add it to the AngularJS template cache. I also use karma for running unit tests, but that's out of scope of this article, so I wont mention it further and it wont be included in the GruntFile.js, but be aware that it's there and I have drunk the TDD kool-aid.

GruntFile.js

'use strict';

    module.exports = function (grunt) {

    // Load grunt tasks automatically
    require('load-grunt-tasks')(grunt);

    grunt.initConfig({
        concat: {
            options: {
                banner:
                    'angular.module(\'PTTP.Widgets.Product\', [\'PTTP.Controllers.Product\',' +
                                                              '\'PTTP.Directives.Product\',' +
                                                              '\'PTTP.Services.Product\',' +
                                                              '\'PTTP.Templates.Product\']);\n'
            },
            dist: {
                src: ['./scripts/*.js'],
                dest: './dist/productWidget.js'
            }
        },
        html2js: {
            app: {
                options: {
                    base: './',
                    useStrict: true,
                    quoteChar: '\'',
                    htmlmin: {
                        collapseBooleanAttributes: true,
                        collapseWhitespace: true,
                        removeAttributeQuotes: true,
                        removeComments: true,
                        removeEmptyAttributes: true,
                        removeRedundantAttributes: true,
                        removeScriptTypeAttributes: true,
                        removeStyleLinkTypeAttributes: true
                    }
                },
                src: ['./views/{,*/}*.html'],
                dest: './scripts/templates.js',
                module: 'Kinetix.Templates.Product'
            }
        }
    });

    grunt.registerTask('default', [
        'html2js',
        'concat'
    ]);

};

What I would like to point out from this file, as simple as it may be, is that we utilize the banner option in the concat task so we don't have to manually build our widget every time - this be grunt work.

Also we use the grunt-html2js task to place our directive's html in the template cache under a unique name so we don't have naming collisions for future modules.

Finally we register both of these tasks under the grunt default task so from the command line we can just type - grunt - and our tasks will be run.

This is the standard semi generic structure I use in order to churn out components with relative ease.

Bower Component In Use

Now that we have our module built and pushed to github or another repo storage site we can start to use it in our project as a bower component.

In your project's bower.json file simply add the git path with or without a branch name as by default bower will use the master branch.

bower.json

{
    "name": "powder-to-the-people-project",
    "version": "0.0.1",
    "dependencies": {
        "angular": "~1.2.22",      
        "pttp-angular-product": "git@bitbucket.org:pttp/pttp-angular-product.git",       
    }
}

Beer time...