0
votes

I am a bit new to AngularJs. I am using Angular UI bootstrap (0.10.0) for modal implementation. I am getting the following errors while testing a modal controller
using AngularJs 1.2.7: TypeError: Attempted to assign to a readonly property
using AngularJs 1.2.12: unknown provider: $modalInstanceProvider <- $modalInstance.

I have gone through a lot of similar questions but couldn't understand what's the problem.

As pointed in the comments by Mahery, $modalInstance is made available for injection in the controller by AngularUI Bootstrap implementation. So, we don't need any effort to "resolve" or make it available somehow. Modal Window Issue (Unknown Provider: ModalInstanceProvider)



This is my main controller that leads to creation of modal instance on clicking open on the partial page.

var SomeCtrl = function($scope, $modal){
$scope.open = function(){
    $scope.modalInstance = $modal.open({
      templateUrl: '/templates/simpleModal.html',
      controller: 'simpleModalController',
    });    

    $scope.modalInstance.result.then(
    function(){
        console.log("clicked OK");
    },
    function(){
        console.log("clicked Cancel");
    });
};

};

someCtrl.$inject = ["$scope", "$modal"];
angular.module('angularApp').controller("someCtrl", SomeCtrl);



This is the modal controller I wish to test if it contains the necessary functions (which I intend to add later)

(function(){
   var SimpleModalController = function($scope, $modalInstance){

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

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

};

   SimpleModalController.$inject = ["$scope", "$modalInstance"];
   angular.module('angularApp').controller("simpleModalController", SimpleModalController);

})();



This is the test I have written for the modal controller

describe('Testing simpleModalController',function() {
    var ctrlScope;
    var modalInstance;
    var ctrl;
    beforeEach(function() {
          module('angularApp');
          inject(function($rootScope, $modalInstance, $controller) {
          ctrlScope = $rootScope.new();
          modalInstance = $modalInstance;
          ctrl = $controller('simpleModalController', 
                 { 
                   $scope : ctrlScope, 
                   $modalInstance : modalInstance
              });
          });
    });

    it('should check existence of scope variables and functions when created', function() {
        console.log('pending test');
    });
});

I have no troubles in the functionality of modal in the app, testing the main controller and integration of modal. But I am unable to test the modal controller. I think the problem is with injection of $modalInstance in the test (of simple modal controller). But as mentioned earlier, angular-ui bootstrap makes it available.

Any help is appreciated. Thanks.

5

5 Answers

0
votes

So.. This is one way of testing it..

describe('Testing',function() {
   it('test',function() {
     inject(function($rootScope, $modal) {  
          var fakeModal = { };
          //Basically, what you want is for your modal's controller to get
          //initalized and then returned to you, so the methods in it can be unit tested

          spyOn(modal, 'open').andReturn(fakeModal);

          ctrl = $controller('Controller',
            {
                $scope : ctrlScope,             
                $modal: modal
             }); 
     });
   });
});
1
votes

Having the next Modal Controller definition:

  angular.module('module').controller('ModalInstanceController',ModalInstanceController);

  function ModalInstanceController($timeout, $modalInstance, $scope) {
    //controller across a bunch of modals
    $scope.closeModal = function(){
      $modalInstance.dismiss('cancel');
    };

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

You can create an spy Object with the needed methods using jasmine, and pass that object to the controller when you create the instance:

 beforeEach(inject(($controller, $timeout, $rootScope) => {
    modalInstance = jasmine.createSpyObj('modalInstance', ['dismiss']);
    scope = $rootScope.$new();
    controller = $controller('ModalInstanceController', {
      $modalInstance: modalInstance,
      $scope: scope
    });
  }));

Later in your test scenarios you can check the spied object:

  it('should defined the required methods on the scope', () => {
    expect(scope.closeModal).toBeDefined();
    expect(scope.action).toBeDefined();
    scope.closeModal();
    expect(modalInstance.dismiss).toHaveBeenCalledWith('cancel');
    scope.action();
    expect(modalInstance.dismiss).toHaveBeenCalledWith();
  });
0
votes

I've been fighting with this issue too. The problem is that the controller your trying to instantiate in your test is a total different instance the $modal service instantiates internally passing in the actual modal window as $modalInstance. See the js code comments at the example http://angular-ui.github.io/bootstrap/#/modal:

// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.

So how do you test your controller then? I've not found the solution yet, im sorry. The diffulty resides in that you can't have access to your controller's scope as the $modal service creates a new scope from whether is the $rootScope or a scope you pass in with your options. So you loose track.

The least you can do is test the functions you passed in to the result promise. This is done by spying on the $modal.open function and returning a mock. And this is shown here https://stackoverflow.com/a/21370703/2202143. And complement with integration tests using tools like protractor.

0
votes

Take a look at the answer that was selected as correct on the question: Unit testing a modalInstance controller with Karma / Jasmine.

I was struggling with the same issue for some time, and that question (and answer) helped me test my modals (and functions to open/close them) in a really clean way!

0
votes

I didn't like any answer given here so I am adding my own.

the reason I didn't like the answers above is that they don't hold once a project is a bit bigger.

for me, the solution was to simply implement an angular service called $modalInstance...

so under spec I create a folder named shims that I use for small items such as these. (make sure to add it to karma.conf)

and there I implement

angular.module(..).service('$modalInstance', function(){
     this.dismiss = jasmine.createSpy('$modalInstance.dismiss'); 
     ... 
});

I find this method much cleaner and much more maintainable and straightforward.

sometimes, I like making sure my shim is only loaded for specific tests, in that case I simply give it a specific module name and then I have to add a module call for it otherwise it won't load.

I also highly recommend using a different library for modals, I recommend ng-dialog for many reasons but in this context I can say it is much more test friendly, been using it for a while now.