I am unit testing an Angular directive and would like to mock or stub in some way the instantiation of the named controller in the unit test.
So first I suppose on to some code...
'use strict'; angular.module('App.Directives.BreadCrumbs', []) .directive('kxBreadcrumbs', function () { return { restrict: 'E', controller: 'BreadCrumbsController', template: '<!-- Breadcrumbs Directive HTML -->' + '<ol class="breadcrumb">' + ' <li ng-repeat="crumb in crumbPath">' + ' <a ng-class="{true: \'disable\', false: \'\'}[crumb.last]" href="{{crumb.href}}" ng-click="updateCrumb(crumb.name)">{{crumb.name}}</a>' + ' </li>' + '</ol>' + '<!-- End of Breadcrumbs Driective HTML -->' }; });
This is one sample directive that I would unit test, the important thing to take away from this is the named controller.
So in my unit test
'use strict'; describe('Directives: Breadcrumbs', function () { var//iable declarations elm, scope, $rootScope ; beforeEach(function () { module('App.Directives.BreadCrumbs'); module('App.Controllers.BreadCrumbs'); module('App.Constants'); // <--- Comes from the controller dependancy }); beforeEach(inject(function (_$rootScope_, $compile) { $rootScope = _$rootScope_; scope = $rootScope.$new(); elm = angular.element('<kx-breadcrumbs></kx-breadcrumbs>'); $compile(elm)(scope); scope.$apply(); })); it('Should create the breadcrumbs template', function () { scope.crumbPath = [{name: 'home', href: '/'},{name: 'coffee', href: '/coffee'},{name: 'milk', href: '/milk'}]; scope.$apply(); var listItem = $(elm).find('li'); expect(listItem.length).toBe(3); expect($(listItem).text()).toContain('home'); expect($(listItem).text()).toContain('coffee'); expect($(listItem).text()).toContain('milk'); }); });
You can see the inclusion of the 3 modules - the directive, the controller and the third one the constants. This is referenced by the controller as a dependancy so in order to pull this into the unit test I need to pull in the dependancy or in much worse cases the dependancies from the controller. But as I am not unit testing the functionality of the controller in the directive unit test, this seem redundant and bloating of code through inclusion of modules. Ideally I would like to only include the module that I am unit testing.
module('App.Directives.BreadCrumbs');
and not (modules added to exemplify my point more)
module('App.Directives.BreadCrumbs'); module('App.Controllers.BreadCrumbs'); module('App.Constants'); // <--- Comes from the controller dependancy module('App.Service.SomeService'); // <--- Comes from the controller dependancy module('App.Service.SomeOtherService'); // <--- Comes from the SomeService dependancy
When we unit test controllers we can mock services that are passed in either completely or by using jasmine spies. Can we accomplish the same sorta thing in unit test of directives so I don't have to follow the dependancy trail?
Introduction. Mocking is a great idea for testing Angular apps because it makes maintenance easier and helps reduce future bugs. There are a few complex tools, such as XUnit, for mocking an Angular CLI project. You can execute the mocking methods described in this guide only if you use vanilla Jasmine + Angular Testbed ...
Correct. You should mock things that depend on anything persistent or external in order to prevent the test from depending on anything persistent or external.
What is mocking? Mocking is a process used in unit testing when the unit being tested has external dependencies. The purpose of mocking is to isolate and focus on the code being tested and not on the behavior or state of external dependencies.
AngularJS is written with testability in mind, but it still requires that you do the right thing. We tried to make the right thing easy, but if you ignore these guidelines you may end up with an untestable application.
You can create mocks in module configuration block by using $controllerProvider.register()
for controllers, $provide.provider()
, $provide.factory()
, $provide.service()
and $provide.value()
for providers, factories and services:
JavaScript
beforeEach(function () { module('App.Directives.BreadCrumbs', function($provide, $controllerProvider) { $controllerProvider.register('BreadCrumbsController', function($scope) { // Controller Mock }); $provide.factory('someService', function() { // Service/Factory Mock return { doSomething: function() {} } }); }); });
Once you do so, Angular will inject your mock BreadCrumbsController
controller into kxBreadcrumbs
directive. This way you don't need to include real controller and it's dependencies into unit test.
For more information see Angular's official documentation on:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With