Background: We’ve been using AngularJS to help with our new order entry system and it’s working great. Testability is at the forefront of angular’s offering and we attempt to have as much valuable code coverage as we can within our application. Early on, we encountered an obstacle while trying to test $q within a controller’s init function.
Obstacle: One of our controllers has to init with a promise object and several of the scope objects within the controller depend on the resolution of the promise.
Controller to test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//setup sample client module to work with, add productModule (a module we don't really want in our test) | |
angular.module("clientModule", ["productModule"]) | |
.controller("ClientCtrl", ["$scope", "productService", function($scope, productService) { | |
$scope.doWork = function(product) { | |
$scope.isAvailable = product.isAvailable; | |
}; | |
var init = function() { | |
productService.then(function(product) { | |
//do work depending on product | |
$scope.doWork(product); | |
}); | |
}; | |
init(); | |
}]); |
- you can see we have a client module
- product module is an external dependency, we don’t want to bring that module into our test
- the product service returns a promise object, most likely $q since we’re using AngularJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//test the promise object | |
describe("Client controller:", function() { | |
var scope; | |
//mock product service used within client controller to reduce scope of test | |
beforeEach(module("productModule", ["$provide", function($provide) { | |
$provide.factory("productService", [function() { | |
var Product = function () { return this; }; | |
Product.prototype.isAvailable = false; | |
Product.prototype.then = function(fn) { | |
this.isAvailable = true; | |
fn(this); //execute then callback from controller | |
}; | |
return new Product(); | |
}]); | |
}])); | |
//only load up client module for test, not full app | |
beforeEach(module("clientModule")); | |
//setup client controller for all tests | |
beforeEach(function() { | |
inject(["$rootScope", "$controller", | |
function($rootScope, $controller) { | |
scope = $rootScope; | |
$controller("ClientCtrl", {$scope: scope}); //create instance of controller | |
}]); | |
}); | |
//begin the tests | |
it("init method is called and $scope.isAvailable is true", function() { | |
//assert | |
expect(scope.isAvailable).toBeTruthy(); | |
}); | |
}); |
- when we first encountered this problem, we were forgetting we were JavaScript developers…it’s easy to get caught up in the JS library itself and expect the library to solve all of your problems for you…can we use $httpBackend here? no…what if your promise isn’t really even working with http? what if it is but you don’t want to test the http call since that (module) is out of scope for this test?…why doesn’t ng do something for us - who cares…JS is easy enough without ng!
- since JS is one of the easiest languages to mock (if not the easiest), just create an object in your beforeEach to resemble the promise you require
- you can see we mock the entire productService & testing the productService itself should be out of scope here
- our test is really simple but it proves the concept for mocking promises