web-dev-qa-db-fra.com

Test du contrôleur d'instance modal Bootstrap de l'interface utilisateur angulaire

Voici une question qui fait suite à celle-ci: Mocking $ modal dans les tests unitaires AngularJS

Le SO référencé est une excellente question avec une réponse très utile. La question qui me reste après cela est toutefois la suivante: comment puis-je tester le contrôleur d'instance modale? Dans le responsable de système référencé, le contrôleur d'appel est testé, mais le contrôleur d'instance modale est simulé. On peut soutenir que ce dernier devrait également être testé, mais cela s’est avéré très délicat. Voici pourquoi:

Je vais copier le même exemple à partir du SO référencé ici:

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

Donc, ma première pensée a été que je voudrais juste instancier le contrôleur directement dans un test, comme n'importe quel autre contrôleur testé:

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

Cela ne fonctionne pas car dans ce contexte, angular n’a pas de fournisseur pour injecter $ modalInstance, car il est fourni par le modal d’UI.

Ensuite, je passe au plan B: utilisez $ modal.open pour instancier le contrôleur. Cela fonctionnera comme prévu:

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

(Remarque: le modèle ne peut pas être une chaîne vide ou il échouera de manière cryptée.)

Le problème maintenant est que je n’ai aucune visibilité sur la portée, qui est la méthode habituelle pour collecter des ressources, etc. Dans le code réel, le contrôleur appelle un service de ressources pour remplir une liste de choix; ma tentative de tester cela définit un expectGet pour satisfaire le service utilisé par mon contrôleur, et je veux valider que le contrôleur insère le résultat dans son champ d'application. Mais le modal crée une étendue new pour le contrôleur d'instance modal (à l'aide de l'étendue que je transmets en tant que prototype) et je ne vois pas comment obtenir un trou de cette étendue. L'objet modalInstance n'a pas de fenêtre dans le contrôleur.

Des suggestions sur la "bonne" façon de tester cela?

(NB: le comportement de la création d'une portée dérivée pour le contrôleur d'instance modale n'est pas inattendu - c'est un comportement documenté. Ma question de savoir comment le tester est toujours valable.)

25
David Pisoni

Je teste les contrôleurs utilisés dans les boîtes de dialogue modales en instanciant directement le contrôleur (de la même manière que vous pensiez le faire précédemment).

Puisqu'il n'y a pas de version fictive de $modalInstance, je crée simplement un objet fictif et le passe au contrôleur.

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

Maintenant, les dépendances du contrôleur sont satisfaites et vous pouvez tester ce contrôleur comme n'importe quel autre contrôleur.

Par exemple, je peux faire spyOn(modalInstance, 'close') et ensuite affirmer que mon contrôleur ferme la boîte de dialogue au moment opportun.

30
Sunil D.

Si vous utilisez jasmine, vous pouvez également vous moquer du $uibModalInstance en utilisant la méthode createSpy:

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

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

Et testez-le sans avoir à appeler spyOn sur chaque méthode, supposons que vous ayez 2 méthodes de portée, cancel() et 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();
});
13
yvesmancera

Le même problème concerne $ uidModalInstance et vous pouvez le résoudre de la même manière:

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

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

ou comme @yvesmancera, vous pouvez utiliser la méthode jasmine.createSpy à la place, par exemple:

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

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

Suivez les étapes ci-dessous:

  • Définir un stub pour ModalInstance comme indiqué ci-dessous

            uibModalInstanceStub = {
                close: sinon.stub(),
                dismiss: sinon.stub()
            };
    
  • Passez le stub d'instance modale lors de la création du contrôleur

        function createController() {
            return $controller(
                ppcConfirmGapModalComponentFullName,
                {
                    $scope: scopeStub,
                    $uibModalInstance: uibModalInstanceStub
                });
        }
    });
    
  • Les méthodes de remplacement close (), licencier () seront appelées dans le cadre des tests

    it ('confirmer modal - vérifier confirmer l'action, on ok () appelle call modalInstance close () function', function () { action = 'Ok'; scopeStub.item = testItem; createController ( ); scopeStub.ok (); });

0
Dilip Nannaware