使用Angular.js + UI路由器进行Karma测试

Bil*_*ill 14 javascript angularjs karma-runner angular-ui-router

我想知道如何使用Angular.js + UI路由器进行Karma测试?

我定义了以下状态:哪个有两个解析器来获取一些数据并为控制器准备数据.(来自Ember背景,这很有道理.)

$stateProvider
  .state('users', {
    resolve: {
      getData: function (User) {
        return User.query().$promise
      },
      stateModels: function (getData) {
        var models = {}
        models.users = getData
        return models
      }
    },
    url: '/',
    templateUrl: '/views/users/index.html',
    controller: 'UsersIndexCtrl'
  })
Run Code Online (Sandbox Code Playgroud)

我们的UserIndexCtrl看起来像:(它接受已解析的stateModels并将其分配给$ scope,因此视图可以使用它)

app.controller('UsersIndexCtrl', [
  '$scope', '$state', 'stateModels',
  function ($scope, $state, stateModels) {

    $scope.users = stateModels.users

  }])
Run Code Online (Sandbox Code Playgroud)

这在浏览器中运行良好,我看到了正确的结果.但是,当涉及到测试时,它给了我奇怪的错误.

以下是Karma单元测试的示例:

describe('controllers', function () {

  var $httpBackend
    , $rootScope
    , $scope
    , $state
    , $httpBackend
    , $controller

  beforeEach(module('app'))

  beforeEach(inject(function ($injector) {
    $state = $injector.get('$state')
    $rootScope = $injector.get('$rootScope')
    $httpBackend = $injector.get('$httpBackend')
    $scope = $rootScope.$new()
    $controller = $injector.get('$controller')
  }))

  it('UserIndexCtrl should exist', inject(function () {
    $httpBackend
      .expect('GET', '/api/users')
      .respond(200, {users: [ {}, {}, {} ]})

    $state.go('users')
    $rootScope.$apply()

    $controller('AdminZonesIndexCtrl', { $scope: $scope, $state: $state });
    $rootScope.$apply()
    assert.equal($scope.users.length, 3)
  }))

})
Run Code Online (Sandbox Code Playgroud)

而且我看到:

[$injector:unpr] Unknown provider: stateModelsProvider <- stateModels
http://errors.angularjs.org/1.3.0-build.2937+sha.4adc44a/$injector/unpr?p0=stateModelsProvider%20%3C-%20stateModels
Error: [$injector:unpr] Unknown provider: stateModelsProvider <- stateModels
http://errors.angularjs.org/1.3.0-build.2937+sha.4adc44a/$injector/unpr?p0=stateModelsProvider%20%3C-%20stateModels
Run Code Online (Sandbox Code Playgroud)

这里的想法是:

  • 我们模拟出API请求,以便对/ api/users的GET请求将返回3个对象的数组.
  • 我们去名为users的州
  • 我们希望看到$ scope.users应该是一个包含3个对象的数组.
  • 通过此测试,我们测试了路由器中定义的两个解析,以及控制器正确分配了已解析的对象.

谢谢比尔

sca*_*rlz 26

您的错误的原因是您首先转换到UsersIndexCtrl使用新范围实例化您的状态,然后在测试中创建另一个控制器实例(同样,使用新范围).这两者是彼此独立的,在第二种情况下,stateModels是未知/不可用的依赖性.

因此,虽然您的想法是有效的测试问题,但在尝试将所有三个一起断言时,您实际上是在单元测试环境中执行端到端测试.你不应该这样做 - 这样做会产生一种脆弱的依赖:

  • 属于"用户"状态的控制器
  • 在状态转换的某个地方调用特定的http请求
  • stateModels依赖项绑定到范围.

在这些断言中,只有最后一个与控制器有任何关系.控制器的单元测试并不关心它实例化的方式/时间或stateModels依赖关系的来源,它们只关心控制器自身的行为.那么,让我们分开这个行为:

单元测试控制器

您的第一次测试应该减少到以下几点:

it('binds the users to the scope', function(){
   var stateModels = [{}, {}, {}];
   $controller('UserIndexCtrl', {$scope: $scope, stateModels: stateModels});
   assert.equal($scope.users, stateModels);
});
Run Code Online (Sandbox Code Playgroud)

请注意,当您添加更多测试时,您可能希望将控制器实例化移动到beforeEach块.

测试路线

测试路径的关注点实际上是应用程序行为的问题,您应该遵循Protractor.但是,如果您特别想对状态执行单元测试,则最好通过测试状态本身的配置来实现.例如:

it('resolves the stateModels dependency', function() {
   var state = $state.get('users');
   assert.isDefined(state.resolve.stateModels); 
   // perform assertion that stateModels function resolves to what is expected
   // Note: any such assertion should stub any dependency being used, to ensure
   // we are testing in isolation.
});
Run Code Online (Sandbox Code Playgroud)

尽管如此,我个人并不选择单元测试路由/路由配置,而是通过使用Protractor的e2e测试获得此类覆盖.