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.