如何模拟指令以启用更高级指令的单元测试?

dnc*_*253 48 unit-testing angularjs angularjs-directive

在我们的应用程序中,我们有几层嵌套指令.我正在尝试为顶级指令编写一些单元测试.我已经嘲笑了指令本身需要的东西,但现在我遇到了来自低级指令的错误.在我对顶级指令的单元测试中,我不想担心低级指令中发生了什么.我只想模拟低级指令,基本上它没有做任何事情,所以我可以单独测试顶级指令.

我尝试通过这样的方式覆盖指令定义:

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

但是,这不会覆盖它,它只是在真正的指令之外运行它.如何阻止这些低级指令在顶级指令的单元测试中做任何事情?

小智 81

指令只是工厂,因此最好的方法是在使用module函数时模拟指令的工厂,通常在beforeEach块中.假设你有一个名为do-something的指令,这个指令被一个名为do-something-else的指令使用,你可以这样模拟它:

beforeEach(module('yourapp/test', function($provide){
  $provide.factory('doSomethingDirective', function(){ return {}; });
}));

// Or using the shorthand sytax
beforeEach(module('yourapp/test', { doSomethingDirective: {} ));
Run Code Online (Sandbox Code Playgroud)

然后,在测试中编译模板时,将覆盖该指令

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

请注意,您需要在名称中添加'Directive'后缀,因为编译器在内部执行此操作:https://github.com/angular/angular.js/blob/821ed310a75719765448e8b15e3a56f0389107a5/src/ng/compile.js#L530

  • 我意识到这一点,但我的观点是,我不应该模拟我正在测试的东西的二级依赖,只有主依赖.换句话说,我不应该关心指令在我的测试中所依赖的内容. (4认同)
  • 如果有人在使用coffescript时遇到这个问题:[ng:areq]参数'fn'不是函数,得到Object - >在beforeEach结束时返回null:https://gist.github.com/jbrowning/9527280 (2认同)

Syl*_*ain 65

模仿指令的干净方法是 $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;
  });
}));
Run Code Online (Sandbox Code Playgroud)

您必须确保模拟获得的优先级高于您正在模拟的指令,并且模拟是终端,以便不会编译原始指令.

priority: 100,
terminal: true,
Run Code Online (Sandbox Code Playgroud)

结果如下所示:

鉴于此指令:

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

你可以像这样嘲笑它:

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);
});
});
Run Code Online (Sandbox Code Playgroud)

还有一些事情:

  • 在创建模拟时,您必须考虑是否需要replace:true和/或a template.例如,如果你模拟ng-src以防止对后端的调用,那么你不想要replace:true而且你不想指定一个template.但如果你嘲笑某些视觉效果,你可能会想要.

  • 如果将优先级设置为100以上,则不会插入mocks的属性.请参阅$ compile源代码.例如,如果你模拟ng-src和设置priority:101,那么你最终ng-src="{{variable}}"不会ng-src="interpolated-value"在你的模拟上.

这是一个有所有东西的plunker.感谢@trodrigues指出我正确的方向.

这是一些解释更多的文档,请查看"配置块"部分.感谢@ebelanger!

  • `$ compileProvider`效果很好.如果你正在使用coffeescript并且得到错误`'fn'不是函数,得到$ CompileProvider`,请确保从`module`函数返回`undefined`. (2认同)

Bas*_*dan 29

由于指令注册的实现,似乎不可能用模拟的指令替换现有的指令.

但是,您可以使用多种方法对较高级别的指令进行单元测试,而不会受到较低级别指令的干扰:

1)不要在单元测试模板中使用低级指令:

如果您的更高级别指令未添加您的低级指令,则在您的单元测试中使用仅包含高级指令的模板:

var html = "<div my-higher-level-directive></div>";
$compile(html)(scope);
Run Code Online (Sandbox Code Playgroud)

因此,较低级别的指令不会干扰.

2)在指令实现中使用服务:

您可以通过服务提供较低级别的指令链接功能:

angular.module("myModule").directive("myLowerLevelDirective", function(myService) {
    return {
        link: myService.lowerLevelDirectiveLinkingFunction
    }
});
Run Code Online (Sandbox Code Playgroud)

然后,您可以在单元测试中模拟此服务,以避免干扰您的更高级别指令.如果需要,该服务甚至可以提供整个指令对象.

3)您可以使用终端指令覆盖您的低级指令:

angular.module("myModule").directive("myLowerLevelDirective", function(myService) {
    return {
        priority: 100000,
        terminal: true,
        link: function() {
            // do nothing
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

使用终端选项和更高的优先级,将不会执行真正的较低级别指令.指令文档中有更多信息.

了解它在Plunker中的工作原理.

  • 如果你必须调整你的代码只是为了让你的测试工作,那么测试就会出现问题. (2认同)

Cin*_*nas 5

您可以在里面修改模板$templateCache以删除任何较低级别的指令:

beforeEach(angular.mock.inject(function ($templateCache) {
  $templateCache.put('path/to/template.html', '<div></div>');
}));
Run Code Online (Sandbox Code Playgroud)