Question

EDIT : Quick & Dirty solution at the end of this post

I am using a modal window from AngularUI-Bootstrap in the same way that it is explained on the website, except that I splitted files. Therefore I have :

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;

    });

and when I test this code with Karma (with the ui-bootstrap-tpls.min.js file loaded in the karma configuration file), I get the following error : Error: [$injector:unpr] [http://errors.angularjs.org/1.2.15-build.2389+sha.c5f2f58/$injector/unpr?p0=%24modalInstanceProvider%20%3C-%20%24modalInstance]1 at Error (native), meaning that jasmine doesn't manage to find the provider for $modalInstance.

I do not even test stuff on this controller, not yet, but here is my jasmine test file :

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 () {

        });
    });

});

Have you got any clue about this problem ? It's not the first "external" module that I use (and test), and I did the same stuff than for the others, except that this time it doesn't work and I have no idea why.

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

EDIT: Quick & probably dirty solution :

Okay, so based on the scope mocking method in the controller instantiation of Jasmine, I figured out how I could "solve" my problem, but it's probably quite dirty, so feel free to comment if you find a better way to do what I intend.

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 () {

        });
    });

});

This way, Jasmine doesn't search for providers anymore, because you already injected the items that are supposed to be needing those providers. It works, but I believe it could be done in a better way...

Était-ce utile?

La solution

I am solving this by just creating mock modal and modalInstance objects and verifying that they have been called by my controller code. Since modal and modalInstance are part of a third party library, it's not our responsibility to test that they work properly - rather, it's our responsibility to test that our code which calls the library is working ok.

Using your example:

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

This way, we don't really need any dependency on the Angular-UI objects and our unit tests are nice and isolated.

Autres conseils

Instead of:

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

This can be written as:

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

Also there is no $modalInstance it is now $uibModalInstance so every "modalInstance" above should be replaced with "uibModalInstance"

+1 for fiznool's answer. it is correct and should be chosen..

I would like to note one thing though, it is not maintainable the way it is presented here.

Since this is angular, I suggest use it..

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

would make your code much more modular and generic. simply add a file under spec somewhere with the above content and make sure to include it in your karma.conf

if you want to make sure it loads only in specific tests, simply give it a unique module name and add it to module invocation in beforeEach

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top