Angularjs 承诺不会在单元测试中解决

我正在使用 jasmine 对 angularjs 控制器进行单元测试,该控制器将作用域上的变量设置为调用返回 promise 对象的服务方法的结果:

I am using jasmine to unit test an angularjs controller that sets a variable on the scope to the result of calling a service method that returns a promise object:

var MyController = function($scope, service) {
    $scope.myVar = service.getStuff();
}

服务内部:

function getStuff() {
    return $http.get( 'api/stuff' ).then( function ( httpResult ) {
        return httpResult.data;
    } );
}

这在我的 angularjs 应用程序的上下文中工作正常,但在 jasmine 单元测试中不起作用.我已经确认then"回调正在单元测试中执行,但 $scope.myVar 承诺永远不会设置为回调的返回值.

This works fine in the context of my angularjs application, but does not work in the jasmine unit test. I have confirmed that the "then" callback is executing in the unit test, but the $scope.myVar promise never gets set to the return value of the callback.

我的单元测试:

describe( 'My Controller', function () {
  var scope;
  var serviceMock;
  var controller;
  var httpBackend;

  beforeEach( inject( function ( $rootScope, $controller, $httpBackend, $http ) {
    scope = $rootScope.$new();
    httpBackend = $httpBackend;
    serviceMock = {
      stuffArray: [{
        FirstName: "Robby"
      }],

      getStuff: function () {
        return $http.get( 'api/stuff' ).then( function ( httpResult ) {
          return httpResult.data;
        } );
      }
    };
    $httpBackend.whenGET( 'api/stuff' ).respond( serviceMock.stuffArray );
    controller = $controller( MyController, {
      $scope: scope,
      service: serviceMock
    } );
  } ) );

  it( 'should set myVar to the resolved promise value',
    function () {
      httpBackend.flush();
      scope.$root.$digest();
      expect( scope.myVar[0].FirstName ).toEqual( "Robby" );
    } );
} );

另外,如果我将控制器更改为以下单元测试通过:

Also, if I change the controller to the following the unit test passes:

var MyController = function($scope, service) {
    service.getStuff().then(function(result) {
        $scope.myVar = result;
    });
}

为什么在单元测试中promise回调结果值没有被传播到$scope.myVar?有关完整的工作代码,请参阅以下 jsfiddle http://jsfiddle.net/s7PGg/5/

Why is the promise callback result value not being propagated to $scope.myVar in the unit test? See the following jsfiddle for full working code http://jsfiddle.net/s7PGg/5/

推荐答案

我猜这个谜团"的关键在于,如果在模板.我的意思是,给定这个控制器:

I guess that the key to this "mystery" is the fact that AngularJS will automatically resolve promises (and render results) if those used in an interpolation directive in a template. What I mean is that given this controller:

MyCtrl = function($scope, $http) {
  $scope.promise = $http.get('myurl', {..});
}

和模板:

<span>{{promise}}</span>

AngularJS 在 $http 调用完成后,将看到"一个承诺已解决,并将使用已解决的结果重新渲染模板.这就是 $q 文档中隐约提到的内容:

AngularJS, upon $http call completion, will "see" that a promise was resolved and will re-render template with the resolved results. This is what is vaguely mentioned in the $q documentation:

$q Promise 被 Angular 模板引擎识别,它意味着在模板中,您可以将附加到范围的承诺视为如果它们是结果值.

$q promises are recognized by the templating engine in angular, which means that in templates you can treat promises attached to a scope as if they were the resulting values.

可以在这里查看代码.

但是,这种魔法"只有在有模板(更准确地说是 $parse 服务)在起作用时才会发生.在您的单元测试中不涉及模板,因此不会自动传播承诺解析.

BUT, this "magic" happens only when there is a template ($parse service, to be more precise) at play. In your unit test there is no template involved so promise resolution is not propagated automatically.

现在,我必须说这种自动解析/结果传播非常方便,但可能会令人困惑,正如我们从这个问题中看到的那样.这就是为什么我更喜欢像您一样显式传播解析结果:

Now, I must say that this automatic resolution / result propagation is very convenient but might be confusing, as we can see from this question. This is why I prefer to explicitly propagate resolution results as you did:

var MyController = function($scope, service) {
    service.getStuff().then(function(result) {
        $scope.myVar = result;
    });
}

相关文章