Fabian Lurz
Fabian Lurz

Reputation: 2039

Inject mockService into an controller initialized from a directive

I want to unit test (karma) a directive, that has a controller with injected services.

See following code:

angular
    .module('webkr')
    .directive('userChangePassword', userChangePassword)
    .directive('compareTo', compareTo);


  /** @ngInject */
  function userChangePassword() {
    var directive = {
      restrict: 'E',
      scope: {},
      templateUrl: 'app/components/account/user_change_password/user_change_password.html',
      controller: changePasswordController,
      controllerAs: 'vm',
      bindToController: true
    };

    return directive;

    /** @ngInject */
    function changePasswordController($log, User, toastr) {
      var _this = this;

      //properties
      _this.formModel = {
        password: null,
        oldPassword: null,
        passwordRepetition: null
      };
      _this.formDefinition = {
        oldPassword: {
          name: 'oldPassword',
          label: 'Altes Passwort',
          placeholder: 'Altes Passwort',
          error: 'old password is required.'
        },
        password: {
          name: 'password',
          label: 'Neues Passwort',
          placeholder: 'Neues Passwort',
          error: 'new password is required.'
        },
        passwordRepetition: {
          name: 'passwordRepetition',
          label: 'Passwort bestätigen',
          placeholder: 'Passwort bestätigen',
          errorRequired: 'old password is required.',
          errorInvalid: 'Confirmation password is not equal new password.'
        }
      };

      //methods

      /**
       * cancel change password procedure
       */
      _this.cancelChangePassword = function () {
        //clean form data

        _this.changePasswordForm.$setPristine();
      };
      /**
       * submit change password
       */
      _this.submitPasswordChange = function () {
        if (_this.changePasswordForm.$invalid) {
          return;
        }

        User.changePassword(_this.formModel).then(function () {
          toastr.info('Password changed', JSON.stringify(_this.formModel));
          _this.cancelChangePassword();
        }, function (err) {
          toastr.error('Can`t change password');
          $log.error(err);
        });
      };
    }
  }

My unit test accordingly:

(function () {
  'use strict';

  describe('directive user_change_password', function () {
    var el, compile, rootScope, controller, mockUserService;

    beforeEach(function () {
      mockUserService = jasmine.createSpyObj('User', ['changePassword'])
      module('webkr', 'ngMockE2E')
    });

    beforeEach(inject(function ($compile, $rootScope, $controller, $q, $log, toastr) {
      compile = $compile;
      rootScope = $rootScope;

      //Service routes used in controller
      mockUserService.changePassword.and.returnValue($q.when("result"));

      //Compile element
      el = compile("<user-change-password></user-change-password>")(rootScope);
      rootScope.$digest();

      //Init controller
      controller = el.controller("userChangePassword", {
        $log: $log,
        User: mockUserService,
        toastr: toastr
      });

      //Spy on the form
      spyOn(controller.changePasswordForm, '$setPristine')

    }));

    it('should be compiled', function () {
      expect(el.html()).not.toEqual(null);
    });
  });
})();

Somehow, the controller is not correctly initialized. When i remove the object ({$log:$log,User:mockUserService,toastr:toastr}) everything is working fine. What am I doing wrong here?

Upvotes: 0

Views: 53

Answers (1)

Estus Flask
Estus Flask

Reputation: 222855

Mocking controller dependencies is applicable to controller-only specs that involve $controller.

el.controller is getter method and has only 1 parameter:

controller(name) - retrieves the controller of the current element or its parent.

At this moment

  //Init controller
  controller = el.controller("userChangePassword", { ... });

the directive was compiled and its controller was instantiated, that's why its instance can be retrieved.

To mock a service when testing directive controllers stick to mocking on injector level:

beforeEach(module('webkr', 'ngMockE2E', function ($provide) {
  $provide.factory('User', function ($q) {
     return { changePassword: jasmine.createSpy().and.returnValue($q.when("result")) };
  });      
}));

Upvotes: 1

Related Questions