你如何测试 angular promises 的延迟?

How do you test angular promises with delays?

我如何运行 一个带有模拟 angular 的测试和一段时间后解决的承诺?

更简单地说:下面的测试永远不会运行

var injector = angular.injector(['ngMock']);
var scope = injector.get('$rootScope').$new();
var q = injector.get('$q');

var promise = function() {
  return q(function(resolve, reject) {
    setTimeout(function() {
      resolve();
    }, 500);
  });
};

promise()
  .then(function() {
    document.getElementById('result').innerHTML = 'TEST RUN';
  });

//resolve the promises
scope.$digest();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular-mocks.js"></script>
<p id="result">starting test...</p>

显然,$q 与 angular 中的 $rootScope.Scope 范围模型观察机制集成在一起,这意味着可以更快地将分辨率或拒绝传播到您的模型中,并避免不必要的浏览器重绘,这将导致闪烁 UI。 (有关更多详细信息,请查看 docs 中 Q 和 $q 之间的差异)。我刚刚将 scope.$apply() 添加到您的代码段中:

var injector = angular.injector(['ngMock']);
var scope = injector.get('$rootScope').$new();
var q = injector.get('$q');

var promise = function() {
  return q(function(resolve, reject) {
    setTimeout(function() {
      resolve();
      scope.$apply();
    }, 500);
  });
};

promise()
  .then(function() {
    document.getElementById('result').innerHTML = 'TEST RUN';
  });

//resolve the promises
scope.$digest();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular-mocks.js"></script>
<p id="result">starting test...</p>

因此您不应该将 setTimeout 功能与 angular 一起使用,而是可以使用 angular 提供的 $timeout 服务。在单元测试中,诸如超时之类的事情将无法像在页面中那样有效地计算和控制。为了解决超时问题,您可以使用 $timeout.flush() 而不是像这样:

var injector = angular.injector(['ngMock']);
var scope = injector.get('$rootScope').$new();
var q = injector.get('$q');
var timeout = injector.get('$timeout');

var promise = function() {
  return q(function(resolve, reject) {
    timeout(function() {
      resolve();
      scope.$apply();
    }, 500);
  });
};

promise()
  .then(function() {
    document.getElementById('result').innerHTML = 'TEST RUN';
  });

//resolve the promises
timeout.flush();
scope.$digest();

好的,我知道了。

这个解决方案实际上是@mido22提出的方案,但我更喜欢一个稍微不同的版本,你可以从$scope和inprog的文档中推导出来。

根据 angular documentation,您需要在处理异步函数时调用 $apply(),如明确指出的 setTimeout。 Here解释更好:

code that is being trigger directly as a call back to some external event, from the DOM or 3rd party library, should expect that it is never called from within Angular, and so any Angular application code that it calls should first be wrapped in a call to $apply."

所以解决方案是将所有会影响 Angular 的调用(包括 resolve() 和 reject())包装到 $apply.

为避免 inprog 错误,不得在另一个内部调用 $apply()。在这个例子中不是这种情况,但是,假设我们有另一个嵌套的 setTimeout,只有最后一个被调用的应该调用 $apply()。

更新:

根据 this,避免 inprog 错误的最佳方法是将非 angular 代码包装在 $timeout() 中。这是 angular 人推荐的策略。

var injector = angular.injector(['ngMock']);
var scope = injector.get('$rootScope').$new();
var q = injector.get('$q');

var promise = function() {
  return q(function(resolve, reject) {
    setTimeout(function() {
      scope.$apply(function() {
        resolve();
      });
    }, 500);
  });
};

promise()
  .then(function() {
    document.getElementById('result').innerHTML = 'TEST RUN';
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.13/angular-mocks.js"></script>
<p id="result">starting test...</p>