【问题标题】:How do you test angular promises with delays?你如何测试有延迟的角度承诺?
【发布时间】:2015-11-05 04:29:51
【问题描述】:

如何使用模拟的 Angular 和经过一段时间解决的 Promise 运行测试?

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

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>

【问题讨论】:

  • 您是否需要让超时实际运行 500 毫秒(或者它可以只是 1 毫秒 1000000 毫秒?),或者只是从超时评估,并让承诺解决?
  • 超时只是一个例子,实际上使用$timeout而不是setTimeout可以轻松解决。关键是,每当异步使用 $q 时,除非您知道何时调用 scope.$digest(); 否则您无法对其进行测试。这并不总是事先知道的。
  • 你为什么要用 Angular 测试非 Angular 代码?
  • 因为我正在制作一些非角度代码,角度友好。具体来说,我正在将 Cordova 插件与 Ionic 集成。我检查了这些人在ngcordova 中所做的事情,他们还使用了 $q 承诺(参见example)。在他们的测试中,他们确实调用了$rootScope.$digest();(参见this),但在我的情况下这行不通:(

标签: javascript angularjs unit-testing promise angular-promise


【解决方案1】:

好的,我知道了。

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

根据angular documentation,当您使用异步函数时,您需要调用$apply(),如明确表示的setTimeout。 Here解释比较好:

作为对来自 DOM 或 3rd 方库的某个外部事件的回调而直接触发的代码,应该期望它永远不会从 Angular 内部调用,因此它调用的任何 Angular 应用程序代码都应该首先被包装在对 $apply 的调用中。”

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

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

更新:

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

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>

【讨论】:

    【解决方案2】:

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

    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>

    【讨论】:

    • 感谢您的建议,但我认为这是不可接受的,原因有两个:1)“promise”函数是正在测试的函数,不应将其逻辑与测试逻辑混为一谈,2)我有在我的代码中尝试了这种方法,我得到一个 inprog 错误
    • @dariosalvi 您收到 inprog 错误,因为它(scope.$apply();) 不安全,一种方法是在执行之前检查状态,另一种方法是使用浏览器的默认 Promise (它是 !@#$ 的 polyfill,就像 IE 一样),在这里使用 $q 有什么特别的原因吗?
    • 我使用 $q 是因为它是标准的 Angular Promise 实现,无需导入任何其他内容,也无需担心不同的 API。也许,当被嘲笑时,你不能以这种方式使用 $q,这对可测试性有点不利。
    【解决方案3】:

    所以你不应该将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();
    

    【讨论】:

    • 感谢您的建议。正如我在第一条评论中所说,超时只是一个例子,我知道我可以使用 $timeout 代替,但这不是我的意思。我的观点是:当我有一些异步代码时,如何使用 $q 进行测试?具体来说,在我的具体情况下,我有一组对 Cordova 插件的调用,所以没有可以使用的角度 $-something。
    猜你喜欢
    • 1970-01-01
    • 2019-03-11
    • 2014-11-16
    • 1970-01-01
    • 2015-03-08
    • 1970-01-01
    • 2015-03-27
    • 2023-03-21
    相关资源
    最近更新 更多