javascript 如何使用 Jasmine 在 AngularJS 中测试 .catch Promise Return?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/23439478/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 00:53:03  来源:igfitidea点击:

How Can I Test a .catch Promise Return in AngularJS using Jasmine?

javascriptangularjsunit-testingjasminepromise

提问by sbrown

I'm fairly new to Javascript and just learning AngularJS but I've gotten most of my test cases to work with some great examples I've found. Unfortunately I can't seem to find anything to help me test my current case.

我对 Javascript 还很陌生,只是在学习 AngularJS,但我已经让我的大部分测试用例与我发现的一些很棒的例子一起工作。不幸的是,我似乎找不到任何可以帮助我测试当前案例的东西。

I'm testing a Controller using a mocked Service whose method returns a promise. I would like the mocked Service to return an error in order to execute the '.catch' block in the controller method. I can tell that it's not getting called correctly in a couple of ways:

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

  1. I'm using istanbulfor code coverage and it's telling me I'm not covering the 'catch'
  2. The code in the '.catch' block is not getting executed from what I can tell via debugging
  1. 我正在使用istanbul进行代码覆盖,它告诉我我没有覆盖“捕获”
  2. '.catch' 块中的代码没有根据我通过调试得知的情况执行

The controller under test, specifically need to test the '.catch' in $scope.login:

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

login.js

登录.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;
        });
      }
    };
  });

The service and method I'm attempting to mock:

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

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

And finally, my test file. The funny part is that all tests are passing but the 'expect' in the last test can be changed to pretty much anything and it still passes. The first two tests seem to run as expected but the last test is where I'm trying to execute the catch block by throwing an error from the mock Auth service:

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

login.unit.js

登录.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 = '[email protected]';
      $scope.user.password = 'password123';

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

      //then
      expect($scope.submitted).toBe(true);
      expect(mockAuthService.login).toHaveBeenCalledWith({email:'[email protected]', 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!');
      });

    });
  });
});

I apologize for posting so much code but I wanted to provide as much context as possible. I really appreciate anyone who can help me out as I learn my way around unit testing Angular and promises! Thanks!!!

我很抱歉发布了这么多代码,但我想提供尽可能多的上下文。我真的很感谢任何能在我学习 Angular 和 promises 单元测试的过程中帮助我的人!谢谢!!!

**UPDATE**

**更新**

I was able to solve my issue with some help from below and discovering some syntactic errors. Here's what fixed this:

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

  1. My beforeEachon the last test was not closed properly and actually enclosed the last test causing it not to run correctly (or maybe at all). This is why changing the expect conditions resulted in no errors.
  2. I changed my beforeEachinject to: mockAuthService.login.andReturn($q.reject({data: {message: 'Bad Login!'}}));using the rejectsuggested below.
  3. Once I properly closed the beforeEachI got an error message that $q was not defined so I had to added it to inject(function($q)
  1. beforeEach在最后一个测试中没有正确关闭并且实际上包含了最后一个测试,导致它无法正确运行(或者根本没有)。这就是为什么更改期望条件不会导致错误的原因。
  2. 我将beforeEach注入更改为:mockAuthService.login.andReturn($q.reject({data: {message: 'Bad Login!'}}));使用以下reject建议。
  3. 正确关闭后,beforeEach我收到一条错误消息,指出 $q 未定义,因此我必须将其添加到inject(function($q)

Once I corrected these issues the promise was correctly rejected and the error was caught by the appropriate code in the controller.

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

采纳答案by Dan Beam

Before or while running your test, mock out part of the environment like this:

在运行测试之前或期间,模拟如下环境的一部分:

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

After the test restore the environment to sanity:

测试后将环境恢复到正常状态:

Auth.login = originalAuthLogin;

This immediately calls the .catch()block of the code you're trying to test.

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