【问题标题】:How Can I Test a .catch Promise Return in AngularJS using Jasmine?如何使用 Jasmine 在 AngularJS 中测试 .catch Promise 返回?
【发布时间】:2014-06-19 19:22:26
【问题描述】:

我对 Javascript 还很陌生,刚刚学习 AngularJS,但我已经让我的大部分测试用例与我发现的一些很好的例子一起工作。不幸的是,我似乎找不到任何东西来帮助我测试我目前的情况。

我正在使用一个模拟服务来测试一个控制器,该服务的方法返回一个承诺。我希望模拟服务返回错误,以便在控制器方法中执行“.catch”块。我可以通过几种方式判断它没有被正确调用:

  1. 我正在使用 istanbul 进行代码覆盖,它告诉我我没有覆盖“捕获”
  2. '.catch' 块中的代码没有按照我通过调试得知的结果执行

被测控制器,具体需要测试$scope.login中的'.catch':

login.js

'use strict';

angular.module('ibcwebDashApp')
  .controller('LoginCtrl', function ($scope, Auth, $location) {
    $scope.user = {};
    $scope.errors = {};

    $scope.login = function(form) {
      $scope.submitted = true;

      if(form.$valid) {
        Auth.login({
          email: $scope.user.email,
          password: $scope.user.password
        })
        .then( function() {
          // Logged in, redirect to home
          $location.path('/');
        })
        .catch( function(err) {
          err = err.data;
          $scope.errors.other = err.message;
        });
      }
    };
  });

我试图模拟的服务和方法:

Auth.login

'use strict';

angular.module('ibcwebDashApp')
  .factory('Auth', function Auth($location, $rootScope, Session, User, $cookieStore) {

    // Get currentUser from cookie
    $rootScope.currentUser = $cookieStore.get('user') || null;
    $cookieStore.remove('user');

    return {

      /**
       * Authenticate user
       * 
       * @param  {Object}   user     - login info
       * @param  {Function} callback - optional
       * @return {Promise}            
       */
      login: function(user, callback) {
        var cb = callback || angular.noop;

        return Session.save({
          email: user.email,
          password: user.password
        }, function(user) {
          $rootScope.currentUser = user;
          return cb();
        }, function(err) {
          return cb(err);
        }).$promise;
      },

最后,我的测试文件。有趣的是,所有测试都通过了,但最后一个测试中的“预期”可以更改为几乎任何东西,它仍然通过。前两个测试似乎按预期运行,但最后一个测试是我试图通过从模拟 Auth 服务抛出错误来执行 catch 块:

login.unit.js

'use strict';

describe('Controller: LoginCtrl', function () {
  var $scope, $location, loginCtrl, mockAuthService;


  beforeEach(function() {
    mockAuthService = jasmine.createSpyObj('Auth', ['login']);

    module('ibcwebDashApp');

    module(function($provide) {
      $provide.value('Auth', mockAuthService);
    });

    inject(function($rootScope, $controller, $q, _$location_) {
      //create an empty scope
      $scope = $rootScope.$new();
      $location = _$location_;
      //declare the controller and inject our empty scope
      loginCtrl = $controller('LoginCtrl', {$scope: $scope, Auth: mockAuthService});

    });

  });


  describe('successful login', function() {

    beforeEach(function() {
      inject(function($q) {
        mockAuthService.login.andReturn($q.when());
      });

    });

    it('should call auth.login with the scope email and password when form is valid', function() {
      //given
      $scope.form = {};
      $scope.form.$valid = true;
      $scope.user.email = 'user@test.com';
      $scope.user.password = 'password123';

      //when
      $scope.login($scope.form);

      //then
      expect($scope.submitted).toBe(true);
      expect(mockAuthService.login).toHaveBeenCalledWith({email:'user@test.com', password:'password123'});

      $scope.$apply(); //force return of auth.login promise

      expect($location.path()).toBe('/');
    });

    it('should not call auth.login if form is invalid', function() {
      //given
      $scope.form = {};
      $scope.form.$valid = false;

      //when
      $scope.login($scope.form);

      expect(mockAuthService.login).not.toHaveBeenCalled();
    });
  });

  describe('unsuccessful login', function() {

    beforeEach(function () {
      inject(function () {
        mockAuthService.login.andReturn($q.when(new Error('Bad Login!')));
      });

      it('should set errors.other to the returned auth error message', function() {
        //given
        $scope.form = {};
        $scope.form.$valid = true;

        //when
        $scope.login($scope.form);

        $scope.$apply();

        //then
        expect($scope.errors.other).toEqual('Bad Login!');
      });

    });
  });
});

我很抱歉发布了这么多代码,但我想提供尽可能多的上下文。我非常感谢任何可以帮助我的人,因为我学习了单元测试 Angular 和 Promise 的方法!谢谢!!!

**更新**

我能够在下面的一些帮助下解决我的问题并发现一些语法错误。以下是解决此问题的方法:

  1. 我在最后一个测试中的beforeEach 没有正确关闭,实际上包含最后一个测试,导致它无法正确运行(或者可能根本没有)。这就是更改预期条件不会导致错误的原因。
  2. 我使用下面建议的rejectbeforeEach 注入更改为:mockAuthService.login.andReturn($q.reject({data: {message: 'Bad Login!'}}));
  3. 当我正确关闭 beforeEach 后,我收到一条错误消息,指出 $q 未定义,因此我必须将其添加到 inject(function($q)

一旦我纠正了这些问题,promise 就会被正确拒绝,并且错误被控制器中的适当代码捕获。

【问题讨论】:

    标签: javascript angularjs unit-testing jasmine promise


    【解决方案1】:

    在运行测试之前或期间,像这样模拟部分环境:

    var originalAuthLogin = Auth.login;
    Auth.login = function() {
      return Promise.reject({data: {message: 'Error message'}});
    };
    

    测试后恢复环境:

    Auth.login = originalAuthLogin;
    

    这会立即调用您要测试的代码的.catch() 块。

    【讨论】:

    • 丹,感谢您的帮助!我是否应该将您的答案标记为解决方案,即使我没有完全按照发布的方式使用它?它确实为我指明了正确的方向。
    • 当然,如果它解决了您的问题,您可以将其标记为解决方案...无论哪种方式对我来说都无关紧要。
    • 就我而言,我不能使用 Promise.reject
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-04
    • 2013-11-12
    • 1970-01-01
    相关资源
    最近更新 更多