【问题标题】:Delay testing of a value until a promise has resolved延迟对值的测试,直到 promise 得到解决
【发布时间】:2017-06-08 05:12:21
【问题描述】:

我正在尝试测试在$onInit 中解决了一个promise 后一个值是否更改为true。我正在尽我所能关注example in this Stack Overflow question/answer。这是我的代码:

class TestCtrl {
  constructor(SearchService) {
    this.testValue = false;
    this.SearchService = SearchService;
  }
  $onInit() {
    this.SearchService.getResults()
      .then(function () {
        this.testValue = true;
      });
  }
}
TestCtrl.$inject = ['SearchService'];

这是我正在尝试运行的测试(使用 mocha、chai、sinon):

it('should work', function() {
  ctrl = $componentController('test', {
    SearchService: SearchService
  }, {});
  sinon.stub(SearchService, 'getResults').resolves({response:{data: 'data'}});
  ctrl.$onInit();
  $rootScope.$apply();
  ctrl.testValue.should.equal(true);
});

我应该在then 中测试ctrl.testValue 吗?另外,使用this example 是不是一个坏主意,因为该示例没有使用带有$onInit 生命周期钩子的组件?

根据我读到的no,“不要在测试中使用 expect 。”但根据我read elsewhere 的内容,我不太确定。

如果我在如何测试承诺(也许存根不是要走的路?)和/或如何测试 $onInit 生命周期钩子中发生的事情中遗漏了一些明显的东西,我不会感到惊讶。

如果问题需要更多详细信息,请提出,我会尽力添加。

【问题讨论】:

  • 没有一般原因您不应该在.then 中使用expect。您链接到的问题中的 OP 是使用 Jasmine,而不是 Mocha。也许 Jasmine 不擅长处理承诺(这将是在 then 中不使用 expect特殊原因,而不是一般原因),但 Mocha 确实如此不擅长处理承诺。你只需要记住回报承诺。
  • 为什么要打电话给$rootScope.apply()
  • 只是为了详细说明 Louis 的回答:Mocha 和 Jasmine 都可以通过让框架将回调传递给测试代码调用的测试函数来处理任何类型的异步代码(包括 Promises)。测试成功完成。 Mocha 有一些特殊的处理方式,它还允许它通过返回承诺来放弃回调,这使得测试更加顺畅(见下文)。
  • 您在 then 处理程序的匿名函数中使用 thisthis.testValue = true; 将分配给window,而不是您的TestCtrl 实例。为了让this 引用TestCtrl 实例,您应该尽可能使用箭头函数,或者将this 包装在变量中,例如var ctrl = this

标签: angularjs unit-testing promise mocha.js sinon


【解决方案1】:

编辑:检查你 $onInit 方法:

$onInit() {
    this.SearchService.getResults() 
      .then(function () {
         // `this` in anonymous function is reffering to window not the controller instance
        this.testValue = true;
      });
  }

$onInit() {
    var self = this;
    self.SearchService.getResults() 
      .then(function () {
        self.testValue = true;
      });
  }

你的例子是正确的

这是在angularjs 中测试异步代码的方法 - 它像同步代码一样被测试。当您执行 $rootScope.$apply() 时,存根的返回承诺得到解决。

为什么它不起作用

stub.resolves() 返回的承诺不是角度承诺。无法使用$rootScope 触发解析,因为它不是 Angular 世界的一部分。它的承诺解析队列与其他东西相关联,因此需要像您通常测试异步代码一样进行测试。

Angular 不依赖于 JavaScript 的原生 Promise 实现 - 它使用 Q 的 promise 库的轻量级实现,该库封装在名为 $q 的服务中

您引用的answer 使用相同的服务从存根创建和返回承诺

为了让您的代码正常工作 - 像测试同步代码一样进行测试 - 您应该返回一个 $q 承诺(通过在 $q.when(value) 中包装一个值)调用 $rootScope.$apply() 将执行 then 中的代码块,然后继续下面的代码 $rootScope.$apply() 行。

这是一个例子:

it('Sinon should work with angular promises', function () {

    var resolved = 'resolved';
    var promise = $q.when(resolved);
    // Our async function
    var stub = sinon.stub().returns(promise);
    // Callback to be executed after the promise resolves
    var handler = sinon.stub(); 

    stub().then(handler); // async code

    // The handler will be called only after $rootScope.$apply()
    handler.callCount.should.equal(0);

    // triggers digest which will resolve `ready` promises 
    // like those created with $q.when(), $q.resolve() or those created 
    // using the $q.defer() and deferred.resolve() was called
    // this will execute the code inside the appropriate callback for
    // `then/catch/finally` for all promises and then continue 
    // with the code bellow -> this is why the test is considered `synchronous`
    $rootScope.$apply();

    // Verify the handler was called and with the expected value
    handler.callCount.should.equal(1);
    handler.should.have.been.calledWith(resolved);

})

它正在行动test promise synchronously in angular

【讨论】:

    【解决方案2】:

    对于初学者,您应该阅读how Mocha expects you to test async code

    从快速开始:

    1. 您走在正确的道路上 - 只是缺少一些位。
    2. 是的,您应该在 then 内进行测试。
    3. 您链接到的示例很好。明白就好。
    4. 绝对没有理由避免在then 中声明测试。事实上,它通常是断言 promise 的已解析值的唯一方法。

    您的测试代码的主要问题是它试图在结果可用之前断言结果(因为承诺会在以后的滴答声中解决,它们是asynchronous)。

    您尝试测试的代码的主要问题是无法知道 init 函数何时解决。

    我们可以通过等待存根 SearchService.getResults 解决来处理 #2(因为我们在测试中控制存根),但这假设对 onInit 的实现有太多了解,所以这是一个糟糕的 hack .

    相反,我们修复TestCtrl 中的代码,只需返回onInit 中的承诺:

    //main code / TestCtrl
    $onInit() {
      return this.SearchService.getResults()
        .then(function () {
          this.testValue = true;
        }); 
    }
    

    现在我们可以简单地等待对onInit 的任何调用解决,然后再测试其执行期间发生的情况!

    为了修复您的测试,我们首先向包装测试函数添加一个参数。 Mocha 会看到这一点并传入一个函数,您可以在测试完成时调用该函数。

    it('should work', function(done) {
    

    这使它成为一个异步测试。现在让我们修复测试部分:

    ctrl.$onInit().then( () => {
      ctrl.testValue.should.equal(true);
      done(); // signals to mocha that the test is finished ok
    }).catch(done); // pass any errors to the callback
    

    您可能还会发现this answer enlightening(如果对您有帮助,请点赞)。读完之后你可能也明白为什么 Mocha 还支持通过从测试中返回一个 Promise 来删除 done 回调。缩短测试时间:

    return ctrl.$onInit().then( () => {
      ctrl.testValue.should.equal(true);
    });
    

    【讨论】:

      【解决方案3】:

      sinon.stub(SearchService, 'getResults').resolves({response:{data: 'data'}}); 没有返回承诺。使用$q。 我建议这样做:

      ctrl = $componentController('test', {
          SearchService: SearchService
      }, {});
      let deferred =$q.defer();
      deferred.resolve({response:{data: 'data'}});
      sinon.stub(SearchService, 'getResults').resolves(deferred.promise);
      ctrl.$onInit();
      $rootScope.$apply();
      ctrl.testValue.should.equal(true);
      

      您无需在 then 中测试 ctrl.testValue。一般来说,我建议不要在你的规范中断言 .then()。如果承诺永远不会得到解决,规范将不会失败。这会给你一种错误的安全感,而实际上你的测试什么也没做。但那只是我的个人意见。 一旦存根返回一个承诺,您的测试就会通过。理想情况下,如果服务正在进行 http 调用,我建议使用 $httpBackend。 干杯。

      【讨论】:

      • 你的第一句话是错误的。 stub.resolves() 确实返回了一个承诺。这就是该功能的全部意义所在。简单的承诺存根。试试这个:stub = sinon.stub().resolves(42); stub().then(res => console.log(res) ); 你可能会想到stub.returns
      • 另外,您的测试没有解决主要问题:使用同步原则测试的异步代码无效。阅读如何使用 Mocha 测试异步代码:mochajs.org/#asynchronous-code
      • 当有一个承诺会改变另一个滴答中的值时,声明“你不需要在 then 中测试 ctrl.testValue”通常也是不正确的。您当然可以使用setTimeout 触发测试以运行相同的效果,但您需要在所需的时间进行猜测。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-31
      • 2019-08-31
      • 2015-02-07
      • 1970-01-01
      相关资源
      最近更新 更多