单元测试视图 - 最佳实践

23t*_*tux 30 javascript testing unit-testing angularjs

有人可以分享单元测试视图的经验吗?我阅读了很多关于如何使用视图进行单元测试的教程,但是一切都有一些缺点.

我提出了以下方法.它有效,但我想知道是否有更好的方法来做到这一点.还有一些缺点,我稍后会解释.我也在使用量角器进行E2E测试,但它们总是很慢,因此我将它们限制在最低限度.

这是我的控制器.它有两个绑定到它的变量$scope,它们在视图中使用:

// test_ctrl.js
angular.module('app', [])
  .controller('TestCtrl', ["$rootScope", "$scope", function ($rootScope, $scope) {
    $scope.bar = "TEST";
    $scope.jobs = [
      {name: "cook"}
    ];
  }]);
Run Code Online (Sandbox Code Playgroud)

视图将$scope.bara <span>$scope.jobs数组转换为ng-repeat指令:

<!-- test.html the view for this controller -->
<span>
  Bar is {{bar || "NOT SET"}}
</span>
<ul>
  <li ng-repeat="job in jobs">{{job.name}}</li>
</ul>
Run Code Online (Sandbox Code Playgroud)

这是测试:

describe('Controller: TestCtrl', function () {
  beforeEach(module('templates'));
  beforeEach(module('app'));

  var TestCtrl, $rootScope, $compile, createController, view, $scope;
  beforeEach(inject(function($controller, $templateCache, _$rootScope_, _$compile_, _$httpBackend_) {
    $rootScope = _$rootScope_;
    $scope = $rootScope.$new();
    $compile = _$compile_;

    createController = function() {
      var html = $templateCache.get('views/test.html');
      TestCtrl = $controller('TestCtrl', { $scope: $scope, $rootScope: $rootScope });
      view = $compile(angular.element(html))($scope);
      $scope.$digest();
    };
  }));

  it('should test the view', function() {
    createController();
    expect(view.find("li").length).toEqual(1)
    console.log($scope.jobs)
  });
});
Run Code Online (Sandbox Code Playgroud)

在该beforeEach功能中,我将设置控制器.该createController函数(从测试本身调用)从中获取一个视图$templateCache,用它自己创建一个控制器$scope,然后它编译模板并触发一个$digest.

模板缓存预先填充了karmas预处理器ng-html2js

// karma.conf.js
...
preprocessors: {
  'app/views/*.html': 'ng-html2js'
}
...
Run Code Online (Sandbox Code Playgroud)

有了这种方法,我有一点问题,还有一些问题:

1.我的对象中的附加$$ hashKey键 ng-repeat

expect($scope.jobs).toEqual([{name: "cook"}]);在我的测试抛出一个错误:

Expected [ { name : 'cook', $$hashKey : '009' } ] to equal [ { name : 'cook' } ]
Run Code Online (Sandbox Code Playgroud)

我知道ng-repeat添加这些密钥,但这是愚蠢的测试.我能想到的唯一方法是分离控制器测试和视图测试.但是当我检查jobs控制器内的阵列时,$$hashKey它不存在.任何想法,为什么会这样?

2.范围问题

当我第一次尝试这个时,我只将我的本地范围定义为,$scope={}而不是$scope = $rootScope.$new()像我在其他控制器测试中所做的那样.但是只有一个普通对象作为本地范围,我无法编译它($compile(angular.element(html))($scope);抛出错误).

我还认为将$rootScope自身作为控制器的当前本地范围传递是一个好主意.这是一个好方法吗?还是有任何缺点,我还没见过呢?

3.最佳做法

我很高兴知道其他人如何在AngularJS中进行单元测试.我认为必须对视图进行测试,因为对于所有的角度指令,它们中存在很多逻辑,我很高兴看到防水;)

Gil*_*man 25

我认为你正在做的是一个单元测试视图的好方法.对于希望单元测试视图的人来说,您的问题中的代码是一个很好的秘诀.


1. ng-repeat $$ hashKey

不要担心数据.相反,测试各种操作的结果,因为这是你在一天结束时真正关心的.因此,在创建控制器之后,以及在模拟s 之后,使用jasmine-jquery来验证DOM的状态click()等.


2. $ scope = $ rootScope.$ new()不是没问题

$rootScopeScope的一个实例,同时$rootScope.$new()创建一个ChildScope实例.使用ChildScope实例进行测试在技​​术上更正确,因为在生产中,控制器范围也是ChildScope的实例.

顺便说一下,创建隔离范围的单元测试指令也是如此.当您$compile使用ChildScope实例执行指令时,将自动创建一个隔离范围(这是Scope的一个实例).您可以使用访问该隔离范围element.isolateScope()

// decalare these variable here so we have access to them inside our tests
var element, $scope, isolateScope;

beforeEach(inject(function($rootScope, $compile) {
  var html = '<div my-directive></div>';

  // this scope is an instance of ChildScope
  $scope = $rootScope.$new();

  element = angular.element(html);   
  $compile(element)($scope);
  $scope.$digest();

  // this scope is an instance of Scope
  isolateScope = element.isolateScope(); 
}));
Run Code Online (Sandbox Code Playgroud)

3. +1测试视图

有人说用Protractor测试视图.当您想要测试整个堆栈时,量角器很棒:前端到后端.然而,量角器很慢,单元测试很快.这就是为什么通过模拟依赖于后端的应用程序的任何部分来测试单元测试的视图和指令是有意义的.

指令是高度可单元测试的.控制器不那么重要.控制器可能有很多移动部件,这可能使它们更难以测试.出于这个原因,我赞成经常创建指令.结果是更模块化的代码更容易测试.