web-dev-qa-db-fra.com

Unité testant un contrôleur modalInstance avec Karma / Jasmine

EDIT: Solution rapide et sale à la fin de ce post

J'utilise une fenêtre modale d'AngularUI-Bootstrap de la même manière qu'elle est expliquée sur le site Web, sauf que j'ai fractionné des fichiers. J'ai donc:

CallingController.js:

$scope.delete = function () {
    if ($scope.selected.length > 0) {
        // [...]
        // preparing data
        // [...]
        var modalInstance = $modal.open({
            templateUrl: 'views/modalView.html',
            controller: 'modalCtrl',
            resolve: {
                itemArray: function () {
                    return $scope.selected;
                }
            }
        });
        modalInstance.result.then(function (confirm) {
            if (confirm === true) {
                // [...]
                // treat
                // [...]
            }
        });
    }
};

modalController.js:

myAppControllers.controller('modalCtrl',
    function ($scope, $modalInstance, itemArray) {

        $scope.accept = function () {
            $modalInstance.close(true);
        };

        $scope.reject = function () {
            $modalInstance.close(false);
        };

        $scope.itemArray = itemArray;

    });

et quand je teste ce code avec Karma (avec le fichier ui-bootstrap-tpls.min.js chargé dans le fichier de configuration de karma), j'obtiens le erreur suivante: Erreur: [$ injector: impr] [ http://errors.angularjs.org/1.2.15-build.2389+sha.c5f2f58/ $ injector/impr? p0 =% 24modalInstanceProvider% 20% 3C-% 20% 24modalInstance] 1 à Erreur (native) , ce qui signifie que le jasmin ne parvient pas à trouver le fournisseur de $ modalInstance.

Je ne teste même pas des trucs sur ce contrôleur, pas encore, mais voici mon fichier de test au jasmin:

testModalController.js:

describe('Controller: modalCtrl', function () {

    beforeEach(module('myApp'));

    var Ctrl;
    var scope;

    // Initialize the controller and a mock scope
    beforeEach(inject(
        function ($controller, $rootScope) {
            scope = $rootScope.$new();

            Ctrl = $controller('modalCtrl', { $scope: scope });
        })
    );

    describe('Initial state', function () {
        it('should instantiate the controller properly', function () {
            expect(Ctrl).not.toBeUndefined();
        });

        it('should initialize its values properly', function () {

        });
    });

});

Avez-vous une idée de ce problème? Ce n'est pas le premier module "externe" que j'utilise (et teste), et j'ai fait le même truc que pour les autres, sauf que cette fois ça ne marche pas et je n'ai aucune idée pourquoi.

========================================== =

EDIT: Solution rapide et probablement sale:

D'accord, donc sur la base de la méthode de simulation de portée dans l'instanciation du contrôleur de Jasmine, j'ai compris comment je pouvais "résoudre" mon problème, mais c'est probablement assez sale, alors n'hésitez pas à commenter si vous trouvez une meilleure façon de faire ce que j'ai l'intention .

testModalController.js:

describe('Controller: modalCtrl', function () {

    beforeEach(module('myApp'));

    var Ctrl;
    var scope;
    var modalInstance;

    // Initialize the controller and a mock scope
    beforeEach(inject(
        function ($controller, $rootScope, _$modal_) {
            scope = $rootScope.$new();
            modalInstance = _$modal_.open({
                templateUrl: 'views/modalView.html'
            });

            Ctrl = $controller('modalCtrl', {
                $scope: scope,
                $modalInstance: modalInstance,
                itemArray: function () { return ['a', 'b', 'c']; }
            });
        })
    );

    describe('Initial state', function () {
        it('should instantiate the controller properly', function () {
            expect(Ctrl).not.toBeUndefined();
        });

        it('should initialize its values properly', function () {

        });
    });

});

De cette façon, Jasmine ne recherche plus de fournisseurs, car vous avez déjà injecté les éléments qui sont censés avoir besoin de ces fournisseurs. Cela fonctionne, mais je pense que cela pourrait être mieux fait ...

25
Manyuuz

Je résous ce problème en créant simplement des objets fictifs modal et modalInstance et en vérifiant qu'ils ont été appelés par mon code de contrôleur. Étant donné que modal et modalInstance font partie d'une bibliothèque tierce, ce n'est pas notre responsabilité de tester leur bon fonctionnement - c'est plutôt notre responsabilité de tester que notre code qui appelle la bibliothèque fonctionne correctement .

En utilisant votre exemple:

describe('Controller: modalCtrl', function () {

  beforeEach(module('myApp'));

  var Ctrl;
  var scope;
  var modalInstance;

  // Initialize the controller and a mock scope
  beforeEach(inject(
    function ($controller, $rootScope) {     // Don't bother injecting a 'real' modal
      scope = $rootScope.$new();
      modalInstance = {                    // Create a mock object using spies
        close: jasmine.createSpy('modalInstance.close'),
        dismiss: jasmine.createSpy('modalInstance.dismiss'),
        result: {
          then: jasmine.createSpy('modalInstance.result.then')
        }
      };
      Ctrl = $controller('modalCtrl', {
        $scope: scope,
        $modalInstance: modalInstance,
        itemArray: function () { return ['a', 'b', 'c']; }
      });
    })
  );

  describe('Initial state', function () {
    it('should instantiate the controller properly', function () {
      expect(Ctrl).not.toBeUndefined();
    });

    it('should close the modal with result "true" when accepted', function () {
      scope.accept();
      expect(modalInstance.close).toHaveBeenCalledWith(true);
    });

    it('should close the modal with result "false" when rejected', function () {
      scope.reject();
      expect(modalInstance.close).toHaveBeenCalledWith(false);
    });
  });
});

De cette façon, nous n'avons pas vraiment besoin de dépendance sur les objets Angular-UI et nos tests unitaires sont Nice et isolés.

63
Tom Spencer

Au lieu de:

modalInstance = {                    // Create a mock object using spies
  close: jasmine.createSpy('modalInstance.close'),
  dismiss: jasmine.createSpy('modalInstance.dismiss'),
  result: {
    then: jasmine.createSpy('modalInstance.result.then')
  }
};

Cela peut s'écrire:

modalInstance = jasmine.createSpyObj('modalInstance', ['close', 'dismiss', 'result.then']);

De plus, il n'y a pas $ modalInstance, c'est maintenant $ uibModalInstance donc chaque "modalInstance" ci-dessus doit être remplacé par "uibModalInstance"

6
Jason Rice

+1 pour la réponse de fiznool. il est correct et doit être choisi ..

Je voudrais cependant noter une chose, elle n'est pas maintenable telle qu'elle est présentée ici.

Puisque c'est angulaire, je suggère de l'utiliser ..

angular.module('...').service('$modalInstance', function(){
   ... define spies and such 
})

rendrait votre code beaucoup plus modulaire et générique. ajoutez simplement un fichier sous spec quelque part avec le contenu ci-dessus et assurez-vous de l'inclure dans votre karma.conf

si vous voulez vous assurer qu'il ne se charge que dans des tests spécifiques, donnez-lui simplement un nom de module unique et ajoutez-le à l'invocation de module dans beforeEach

2
guy mograbi