angularjs - 使用包含 ng-if 的模板对 AngularJS 指令进行单元测试

标签 angularjs unit-testing jasmine

我正在尝试测试下一个 Angular 指令。它监听 ui-route $stateChange 事件以显示进度指示器。

angular.module('sm.components.progress', ['ui.router'])

.directive('smProgress', function($rootScope) {
  return {
    restrict: 'E',
    replace: true,
    template: '<div class="in-progress" ng-if="isRouteLoading">' +
                '<span>.</span><span>.</span><span>.</span>' +
              '<div/>',

    controller: function($scope) {
      $scope.isRouteLoading = false;

      $rootScope.$on('$stateChangeStart',
        function() {
          $scope.isRouteLoading = true;
        });

      $rootScope.$on('$stateChangeSuccess',
        function() {
          $scope.isRouteLoading = false;
        });
    }
  };
});

这是我的测试代码:

describe('smProgress', function() {
  var $compile, $rootScope, element, scope;

  beforeEach(module('sm.components.progress'));

  beforeEach(inject(function(_$compile_, _$rootScope_) {
    $compile = _$compile_;
    $rootScope = _$rootScope_;
    scope = $rootScope.$new();
  }));

  afterEach(function() {
    element.remove();
  });


  it('$rootScope.isRouteLoading should be false on start',
     function() {

    element = $compile('<sm-progress></sm-progress>')(scope);
    scope.$digest();

    expect(scope.isRouteLoading).toBe(false);

    $rootScope.$emit('$stateChangeStart');

    //The directive listen to the $stateChangeStart and     
    //scope.isRouteLoading become true as expected
    expect(scope.isRouteLoading).toBe(true);
    scope.$digest();

    // checks if the template is rendered when ng-if evaluates true
    // how?

  }));

});

模板中的 ng-if 开始评估为 false,因此该元素从 DOM 中删除。当我手动发出 $stateChangeStart 事件时,指令监听器工作,但我在 DOM 中找不到元素。

ng-if 再次评估为 true 时,如何检查模板是否已添加到 DOM 中?

最佳答案

嗯,这就是我找到的解决方案。

需要注意两点:

  1. 指令将 replace 设置为 true .
  2. 模板有一个 ng-if 指令。

会发生这样的事情:

在测试中,指令编译时:

element = $compile('<sm-progress></sm-progress>')(scope);

<sm-progress></sm-progress>替换为:

<div class="in-progress" ng-if="isRouteLoading">
  <span>.</span><span>.</span><span>.</span>
<div/>

同时,ng-if 为 false,所以最终渲染的代码只是一个注释。这就是 ng-if 的自然运作方式。

<!-- ngIf: isRouteLoading -->

$stateChangeStart 被触发时,scope.isRouteLoading 转到 true ,这使得 ng-if 将模板重新插入到 DOM。但在那一点 element 等于:

<!-- ngIf: isRouteLoading -->

不可能在里面找到指令模板。

// THE TEST FAILS
var progressEl = angular.element(element).find('in-progress');
expect(progressEl.length).toBeGreaterThan(0);

解决方案是将 html 文本包装在 div 或任何其他元素中。所以 ng-if 将在其中运行,我们可以检查接下来会发生什么。

it('scope.isRouteLoading should be false on start', function() {

    // wrap sm-progress with a div tag
    element = $compile('<div><sm-progress></sm-progress></div>')(scope);
    scope.$digest();

    expect(scope.isRouteLoading).toBe(false);

    // In this point element compiles to:
    // <div class="ng-scope"><!-- ngIf: isRouteLoading --></div>

    $rootScope.$emit('$stateChangeStart');
    expect(scope.isRouteLoading).toBe(true);
    scope.$digest();

    // Now element compiles to:
    // <div class="ng-scope">
    //     <!-- ngIf: isRouteLoading -->
    //     <div class="sm-progress ng-scope" ng-if="isRouteLoading">
    //         <span>.</span><span>.</span><span>.</span>
    //     </div>
    //     <!-- end ngIf: isRouteLoading -->
    // </div>

    // Now the test pass.
    var progressEl = angular.element(element).find('.in-progress');
    expect(progressEl.length).toBeGreaterThan(0); //element is present

  });

带有代码的 jsfiddle.net http://jsfiddle.net/fwxgyjwc/13/

我希望我已经解释得足够好。我的英语不是很好。

关于angularjs - 使用包含 ng-if 的模板对 AngularJS 指令进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34848102/

相关文章:

testing - RxJs 大理石测试重试时

angularjs - angular scope.$parent 为空,是错误还是功能?

javascript - Angular 和 requirejs,带有注入(inject)器的提供者

angularjs - 如何在 Angular JS 中取消挂起的 API 调用

javascript - POST 请求中的测试和 stub 参数

javascript - Angular2 karma 测试 - 如何制作表单字段 "dirty"?

javascript - Socket.IO双连接

java - 通过更改最终静态变量来测试方法

python - 如何测试写入文件的Python函数

javascript - 测试 Angular $emit 和 $on 事件