Karma Testing с помощью Angular.js + UI Router

Мне было интересно, как следует тестировать с помощью теста Karma с маршрутизатором Angular.js + UI?

У меня определено следующее состояние: у которого есть два разрешения, которые извлекают некоторые данные и подготавливают данные для контроллера. (Исходя из 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'
  })

Наш UserIndexCtrl выглядит следующим образом: (который принимает разрешенное состояниеModels и присваивает его переменной $scope, поэтому представление может использовать его)

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

    $scope.users = stateModels.users

  }])

Это отлично работает в браузере, я вижу правильные результаты. Однако, когда дело доходит до тестирования, это дает мне нечетные ошибки.

Вот пример Karma unit test:

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)
  }))

})

И я вижу:

[$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

Идея здесь:

  • Мы изматываем запрос API, чтобы GET-запросы к /api/users возвращали массив из 3 объектов.
  • Идем в состояние с именем users
  • Мы ожидаем увидеть, что $scope.users должны быть массивом из трех объектов.
  • В этом тесте мы протестировали как разрешения, определенные в маршрутизаторе, так и контроллер правильно назначил разрешенные объекты.

Спасибо Билл

Ответы

Ответ 1

Причиной вашей ошибки является то, что вы сначала переходите к состоянию, которое создает ваш UsersIndexCtrl с новой областью, но затем создает другой экземпляр контроллера (опять же, с новой областью) в тесте. Эти два независимы друг от друга, а во втором случае stateModels - неизвестная/недоступная зависимость.

Итак, хотя ваши идеи являются действительными проблемами тестирования, пытаясь утвердить все три вместе, вы, по сути, выполняете сквозной тест в среде unit test. Вы не должны этого делать - это создаст хрупкую зависимость от:

  • контроллер, принадлежащий государству "пользователи"
  • что где-то в состоянии перехода определенный HTTP-запрос называется
  • что зависимость stateModels привязана к области.

Из этих утверждений только последний относится к контроллеру. Групповые тесты для вашего контроллера не волнуют, как/когда он был создан или откуда возникла зависимость stateModels, они касаются только собственного поведения контроллера. Итак, позвольте разделить это поведение:

Модуль тестирования контроллера

Ваш первый тест должен быть сведен к следующему:

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

Обратите внимание, что по мере добавления дополнительных тестов вы, скорее всего, захотите перенести экземпляр вашего контроллера в блок beforeEach.

Тестирование маршрута

Проблема тестирования маршрута - это действительно поведение приложения, для которого вы должны отнестись к Protractor. Однако, если вы особенно хотите выполнить unit test в состоянии, это, пожалуй, лучше всего проверяется на настройке самого состояния. Например:

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.
});

Несмотря на это, я лично не выбираю конфигурацию маршрутизации/маршрутизации unit test и вместо этого получаю такое покрытие через тестирование e2e с помощью Protractor.