Тестирование модульного контроллера экземпляра AngularUI Bootstrap

Это несколько следующий вопрос к этому: Mocking $modal в модульных тестах AngularJS

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

Я скопирую тот же пример из ссылки SO здесь:

.controller('ModalInstanceCtrl', function($scope, $modalInstance, items){
  $scope.items = items;
  $scope.selected = {
    item: $scope.items[0]
  };

  $scope.ok = function () {
    $modalInstance.close($scope.selected.item);
  };

  $scope.cancel = function () {
    $modalInstance.dismiss('cancel');
  };
});

Итак, моя первая мысль заключалась в том, что я просто создаю экземпляр контроллера непосредственно в тесте, как и любой другой тестируемый контроллер:

beforeEach(inject(function($rootScope) {
  scope = $rootScope.$new();
  ctrl = $controller('ModalInstanceCtrl', {$scope: scope});
});

Это не работает, потому что в этом контексте angular не имеет провайдера для ввода $modalInstance, поскольку он предоставляется модальным интерфейсом.

Затем я перехожу к плану B: используйте $modal.open для создания экземпляра контроллера. Это будет работать как ожидалось:

beforeEach(inject(function($rootScope, $modal) {
  scope = $rootScope.$new();
  modalInstance = $modal.open({
    template: '<html></html>',
    controller: 'ModalInstanceCtrl',
    scope: scope
  });
});

(Обратите внимание, что шаблон не может быть пустой строкой, иначе он будет ошибочным.)

Теперь проблема заключается в том, что я не вижу видимости в области, которая является обычным способом сбора ресурсов unit test и т.д. В моем реальном коде контроллер вызывает службу ресурсов для заполнения списка вариантов; моя попытка проверить это устанавливает ожидать, что GET удовлетворяет службе, которую использует мой контроллер, и я хочу проверить, что контроллер помещает результат в свою область. Но модальный создает новую область для модального контроллера экземпляра (используя область действия, которую я передаю в качестве прототипа), и я не могу понять, как я могу получить отверстие этой области. Объект modalInstance не имеет окна в контроллере.

Любые предложения по "правильному" способу проверить это?

(N.B.: поведение создания области производных для модального контроллера экземпляра не является неожиданным - это документированное поведение. Мой вопрос о том, как его проверить, по-прежнему действителен независимо.)

Ответ 1

Я тестирую контроллеры, используемые в модальных диалогах, напрямую создавая экземпляр контроллера (так же, как вы изначально предполагали сделать это выше).

Так как там нет издевательства версии $modalInstance, я просто создаю mock-объект и передаю его в контроллер.

var modalInstance = { close: function() {}, dismiss: function() {} };
var items = []; // whatever...

beforeEach(inject(function($rootScope) {
  scope = $rootScope.$new();
  ctrl = $controller('ModalInstanceCtrl', {
      $scope: scope, 
      $modalInstance: modalInstance, 
      items: items
  });
}));

Теперь зависимости для контроллера выполнены, и вы можете протестировать этот контроллер, как и любой другой контроллер.

Например, я могу сделать spyOn(modalInstance, 'close'), а затем утверждать, что мой контроллер закрывает диалог в соответствующее время.

Ответ 2

В качестве альтернативы, если вы используете жасмин, вы можете издеваться над $uibModalInstance с помощью метода createSpy:

beforeEach(inject(function ($controller, $rootScope) {
  $scope = $rootScope.$new();
  $uibModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);

  ModalCtrl = $controller('ModalCtrl', {
    $scope: $scope,
    $uibModalInstance: $uibModalInstance,
  });
}));

И протестируйте его, не называя spyOn для каждого метода, скажем, у вас есть 2 метода scope, cancel() и confirm():

it('should let the user dismiss the modal', function () {
  expect($scope.cancel).toBeDefined();
  $scope.cancel();
  expect($uibModalInstance.dismiss).toHaveBeenCalled();
});

it('should let the user confirm the modal', function () {
  expect($scope.confirm).toBeDefined();
  $scope.confirm();
  expect($uibModalInstance.close).toHaveBeenCalled();
});

Ответ 3

То же самое происходит с $uidModalInstance, и вы можете решить его аналогичным образом:

var uidModalInstance = { close: function() {}, dismiss: function() {} };

$ctrl = $controller('ModalInstanceCtrl', {
   $scope: $scope,
   $uibModalInstance: uidModalInstance
});

или, как сказал @yvesmancera, вы можете использовать метод jasmine.createSpy, например:

var uidModalInstance = jasmine.createSpyObj('$uibModalInstance', ['close', 'dismiss']);

$ctrl = $controller('ModalInstanceCtrl', {
   $scope: $scope,
   $uibModalInstance: uidModalInstance
});

Ответ 4

Следуйте приведенным ниже шагам:

  • Определите заглушку для ModalInstance, как показано ниже

            uibModalInstanceStub = {
                close: sinon.stub(),
                dismiss: sinon.stub()
            };
    
  • Передайте заглушку модального экземпляра при создании контроллера

        function createController() {
            return $controller(
                ppcConfirmGapModalComponentFullName,
                {
                    $scope: scopeStub,
                    $uibModalInstance: uibModalInstanceStub
                });
        }
    });
    
  • Методы Stub close(), reject() будет вызван как часть тестов

    it ('confirm modal - подтвердить действие подтверждения, по вызову ok() modalInstance close() function', function() {                       action = 'Ok';                       scopeStub.item = testItem;                       createController();                       scopeStub.ok(); });