Unit Testing Angular Directives with bindToController
Angular’s controllerAs
and bindToController
directive options have worked wonders to enforce good practices on my code. However, these can make other good practices, namely unit testing, a bit less straightforward. A bit of extra plumbing in your tests (and ngMocks
greater than 1.3.15
) can easily work around these issues.
$controller Service and Binding Parameters
ngMocks 1.3.15 introduced a third parameter to the $controller
service that allows tests to inject controller bound parameters. The Angular documentation explains the functionality very succinctly. An example using jasmine tests follows below:
<!DOCTYPE html>
<html ng-app="example">
<head>
<link data-require="jasmine@*" data-semver="2.2.1" rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.css" />
<script data-require="jasmine@*" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.js"></script>
<script data-require="jasmine@*" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine-html.js"></script>
<script data-require="jasmine@*" data-semver="2.2.1" src="http://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/boot.js"></script>
<script data-require="angular.js@~1.3.15" data-semver="1.3.17" src="https://code.angularjs.org/1.3.17/angular.js"></script>
<script data-require="angular-mocks@1.3.15" data-semver="1.3.15" src="https://code.angularjs.org/1.3.15/angular-mocks.js"></script>
<script src="script.js"></script>
</head>
<body>
<h3>Odd Numbers</h3>
<ex-odd-number-lister in-items="[1, 2, 3, 4, 5]"></ex-odd-number-lister>
</body>
</html>
// module
(function() {
angular.module('example', []);
})();
// directive
(function() {
function OddNumberLister() {
return {
controller: 'ExampleController',
controllerAs: 'vm',
bindToController: true,
scope: {
inItems: '=',
},
template: "<ul><li ng-repeat='oddItem in vm.oddDataItems()'></li></ul>",
};
}
angular.module('example').directive('exOddNumberLister', OddNumberLister);
})();
// controller
(function() {
function ExampleController() {
var vm = this;
vm.oddDataItems = function() {
return vm.inItems.filter(function(item) {
return 1 === (item % 2);
});
}
}
angular.module('example').controller('ExampleController', ExampleController);
})();
// tests
(function() {
describe('ExampleController', function() {
beforeEach(module('example'));
var exampleController;
var expectedInItems;
beforeEach(inject(function($controller) {
expectedInItems = [1, 2, 3];
var locals = {};
var bindingsParams = {
inItems: expectedInItems,
};
exampleController = $controller('ExampleController', locals, bindingsParams);
console.log(exampleController.inItems);
}));
describe('when it is constructed', function() {
it('can be constructed', function() {
expect(exampleController).toBeDefined();
expect(exampleController).not.toBeNull();
});
it('sets inItems from the scope', function() {
expect(exampleController.inItems).toBe(expectedInItems);
});
});
describe('oddDataItems()', function() {
it('filters even items', function() {
expect(exampleController.oddDataItems().length).toBe(2);
expect(exampleController.oddDataItems()[0]).toBe(1);
expect(exampleController.oddDataItems()[1]).toBe(3);
});
});
});
})();
Demo here (plnkr)
comments powered by Disqus