关闭用于茉莉花测试的登录模块的问题

时间:2015-07-24 23:10:34

标签: angularjs unit-testing karma-jasmine

我有一个应用程序使用angulars $ modal在用户尝试输入没有有效身份验证令牌的安全路由时弹出登录模式。这很好用,但是导致测试问题。

模态是作为工厂创建的

  .factory('loginModal', function ($modal) {
      return function() {
          var instance = $modal.open({
            templateUrl: 'partials/login',
            controller: 'AuthCtrl',
            controllerAs: 'AuthCtrl'
          })

        return instance.result;
      };
    });

在我的控制器中,我有一个登录操作,成功登录模式后,使用$ scope关闭。$ close。

$scope.login = function() {
      auth.login($scope.user)
        .then(function(response) {
          $scope.$close(response);
          $state.go('secure.user');
        }, function(response) {
          $scope.hasErrMsg = true;
          $scope.errMsg = 'Incorrect password.';
          $scope.$dismiss;
        });
    };

最后我的单元测试正在检查以确保在调用我的控制器登录功能时使用正确的属性调用auth.login。

describe('Auth Controller Tests', function () {
    var $scope, $controller, $q, $httpBackend, auth, controller, deferred, loginReqHandler, userReqHandler, indexReqHandler, registerPostReqHandler, doesUserExistPostReqHandler, loginPostReqHandler, loginModal;

    beforeEach(module('enigmaApp'));

    beforeEach(inject(function ($injector) {
        $scope = $injector.get('$rootScope');
        $controller = $injector.get('$controller');
        $q = $injector.get('$q');
        $httpBackend = $injector.get('$httpBackend');
        auth = $injector.get('auth');
        controller = $controller('AuthCtrl', { $scope: $scope });
        deferred = $q.defer();
        spyOn(auth, 'isLoggedIn');
        loginReqHandler = $httpBackend.when('GET', 'partials/login').respond(deferred.promise);
        userReqHandler = $httpBackend.when('GET', 'partials/user').respond(deferred.promise);
        indexReqHandler = $httpBackend.when('GET', 'partials/index').respond(deferred.promise);
        registerPostReqHandler = $httpBackend.when('POST', '/register').respond(deferred.promise);
        doesUserExistPostReqHandler = $httpBackend.when('POST', '/doesUserExist').respond(deferred.promise);
        loginPostReqHandler = $httpBackend.when('POST', '/login').respond(deferred.promise);
        loginModal = $injector.get('loginModal');
    }));

    afterEach(function () {
        $httpBackend.flush();
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });
    describe('AuthCtrl.login()', function () {
        it('should call auth.login() with $scope.user', function () {
            $scope.user = {
                email: 'bwayne@wayneenterprise.com',
                password: 'password123'
            };
            spyOn(auth, 'login').and.returnValue(deferred.promise);
            $scope.login();
            deferred.resolve();
            $scope.$digest();
        expect(auth.login).toHaveBeenCalledWith($scope.user);
        });
    });
});

现在,当我运行测试时,我收到以下错误:

TypeError: $scope.$close is not a function

我怀疑这个错误是因为代码期望$ scope在调用$ scope时设置为模式的范围。$ close和我的测试$ scope设置为控制器范围。虽然我不确定如何引用$ modal的范围。

更新:我刚刚发现是否添加$ scope。$ close = function(){};在it()块内,然后测试正常运行。这是正确的方法吗?

1 个答案:

答案 0 :(得分:0)

我想你可能试图测试太多了。如果您需要进行所有$http次调用,并且只是为了测试控制器,那么您几乎肯定会做错事。

这就是我测试控制器的方法。有关详细说明,请参阅注释我意识到这可能不适合你的用例,但希望你会发现看到不同的方法很有帮助。

DEMO

<强> appSpec.js

describe('Auth Controller Tests', function () {

    var $scope, $controller, $state, auth, controller,
        loginDeferred, $closeSpy, goSpy, loginSpy;

    beforeEach(module('enigmaApp'));

    beforeEach(inject(function($q, _$controller_, _$rootScope_){

        $controller     = _$controller_;
        $scope          = _$rootScope_.$new();
        loginDeferred   = $q.defer();

        // create spies
        $closeSpy       = jasmine.createSpy('$close');  
        goSpy           = jasmine.createSpy('go');
        loginSpy        = jasmine
                          .createSpy('login')
                          .and
                          .returnValue(loginDeferred.promise);

        // create mock services with spies
        $scope.$close = $closeSpy;

        auth = {
          login   : loginSpy
        };

        $state = {
          go: goSpy
        }

        // initiate controller and inject mocks
        controller = $controller('AuthCtrl', { 
          $scope: $scope, 
          auth: auth, 
          $state: $state

        });


        // manual $digest to update our controller
        // with our mocked services and scope
        $scope.$digest();

    }));

    describe('AuthCtrl.login()', function () {

        it('should call auth.login() with $scope.user', function () {

            // define mock user object on our $scope
            $scope.user = {
                email: 'bwayne@wayneenterprise.com',
                password: 'password123'
            }; 

            // call login() which in turn calls our
            //  loginSpy
            $scope.login();


            // just assert that our loginSpy was called with
            // the mockUser
            // we don't care about anything else so no need 
            // to worry about promises etc.
            expect(auth.login).toHaveBeenCalledWith($scope.user);
        });


        it('should call $state.go on succesful login', function(){
          // call login which will
          // call our authLogin spy that returns 
          // the loginDeferred promise
          $scope.login();

          // manually resolve the loginDeferred promise and 
          // call $digest to trigger the then() callback
          loginDeferred.resolve({});
          $scope.$digest();

          // assert $state.go is called when 
          // our then callback it triggered.
          expect($state.go).toHaveBeenCalledWith('secure.user');
        });

        it('should set the errMsg to true if the login fails', function(){

          expect($scope.hasErrMsg).toBeUndefined();

          $scope.login();

          // this time reject our promise
          // so we can evaluate the catch callback
          loginDeferred.reject({});
          $scope.$digest();

          expect($scope.hasErrMsg).toBe(true);

        });

    });


});

<强> app.js

var app = angular.module('enigmaApp', ['ui.router', 'ui.bootstrap']);

app.controller('AuthCtrl', function($scope, auth, $state){

  // Warning: OPINIONATED CODE
  // I refactored your auth login function
  // to use the then and catch methods which I 
  // think are much cleaner
  $scope.login = function() {
      auth
        .login($scope.user)
        .then(function(response) {
          $scope.$close(response);
          $state.go('secure.user');
        })
        .catch(function(response) {
          $scope.hasErrMsg = true;
          $scope.errMsg = 'Incorrect password.';
          $scope.$dismiss;
        });
    };

});