Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you mock directives to enable unit testing of higher level directive?

In our app we have several layers of nested directives. I'm trying to write some unit tests for the top level directives. I've mocked in stuff that the directive itself needs, but now I'm running into errors from the lower level directives. In my unit tests for the top level directive, I don't want to have to worry about what is going on in the lower level directives. I just want to mock the lower level directive and basically have it do nothing so I can be testing the top level directive in isolation.

I tried overwriting the directive definition by doing something like this:

angular.module("myModule").directive("myLowerLevelDirective", function() {     return {         link: function(scope, element, attrs) {             //do nothing         }     } }); 

However, this does not overwrite it, it just runs this in addition to the real directive. How can I stop these lower level directives from doing anything in my unit test for the top level directive?

like image 862
dnc253 Avatar asked Jul 08 '13 17:07

dnc253


People also ask

How to mock a directive in Angular?

A mock directive in Angular tests can be created by MockDirective function. The mock directive has the same interface as its original directive, but all its methods are dummies. In order to create a mock directive, pass the original directive into MockDirective function.

What is a directive in API?

As you can see, the directive defines a api property which is attached to a JavaScript object with functions on it. From the outside of the directive you can now bind to that API object and invoke operate using its functions.


2 Answers

Directives are just factories, so the best way to do this is to mock the factory of the directive in using the module function, typically in the beforeEach block. Assuming you have a directive named do-something used by a directive called do-something-else you'd mock it as such:

beforeEach(module('yourapp/test', function($provide){   $provide.factory('doSomethingDirective', function(){ return {}; }); }));  // Or using the shorthand sytax beforeEach(module('yourapp/test', { doSomethingDirective: {} )); 

Then the directive will be overridden when the template is compiled in your test

inject(function($compile, $rootScope){   $compile('<do-something-else></do-something-else>', $rootScope.$new()); }); 

Note that you need to add the 'Directive' suffix to the name because the compiler does this internally: https://github.com/angular/angular.js/blob/821ed310a75719765448e8b15e3a56f0389107a5/src/ng/compile.js#L530

like image 162
trodrigues Avatar answered Oct 10 '22 00:10

trodrigues


The clean way of mocking a directive is with $compileProvider

beforeEach(module('plunker', function($compileProvider){   $compileProvider.directive('d1', function(){      var def = {       priority: 100,       terminal: true,       restrict:'EAC',       template:'<div class="mock">this is a mock</div>',     };     return def;   }); })); 

You have to make sure the mock gets a higher priority then the directive you are mocking and that the mock is terminal so that the original directive will not be compiled.

priority: 100, terminal: true, 

The result would look like the following:

Given this directive:

var app = angular.module('plunker', []); app.directive('d1', function(){   var def =  {     restrict: 'E',     template:'<div class="d1"> d1 </div>'   }   return def; }); 

You can mock it like this:

describe('testing with a mock', function() { var $scope = null; var el = null;  beforeEach(module('plunker', function($compileProvider){   $compileProvider.directive('d1', function(){      var def = {       priority: 9999,       terminal: true,       restrict:'EAC',       template:'<div class="mock">this is a mock</div>',     };     return def;   }); }));  beforeEach(inject(function($rootScope, $compile) {   $scope = $rootScope.$new();   el = $compile('<div><d1></div>')($scope); }));  it('should contain mocked element', function() {   expect(el.find('.mock').length).toBe(1); }); }); 

A few more things:

  • When you create your mock, you have to consider whether or not you need replace:true and/or a template. For instance if you mock ng-src to prevent calls to the backend, then you don't want replace:true and you don't want to specify a template. But if you mock something visual, you might want to.

  • If you set priority above 100, your mocks's attributes will not be interpolated. See $compile source code. For instance if you mock ng-src and set priority:101, then you'll end-up with ng-src="{{variable}}" not ng-src="interpolated-value" on your mock.

Here is a plunker with everything. Thanks to @trodrigues for pointing me in the right direction.

Here is some doc that explains more, check the "Configuration Blocks" section. Thanks to @ebelanger!

like image 43
Sylvain Avatar answered Oct 10 '22 01:10

Sylvain