【问题标题】:Mocha: assertions within promise not workingMocha:承诺中的断言不起作用
【发布时间】:2014-07-16 17:29:44
【问题描述】:

所以,我对 mocha 完全陌生,并且承诺,我被要求编写集成测试,将表单数据发布到页面,获取身份验证密钥,然后对密钥运行测试。

我正确地取回了密钥,这由我的日志语句确认,但是当我运行 mocha 时,我的测试显示 0 通过。它甚至没有打印出 'describe' 和 'it' 语句的描述,这意味着它们在 Promise 中不起作用。有没有我可以完成这项工作,同时仍然让断言访问我的授权密钥?

require('should');
require('sails');
var Promise = require("promise");
var request = require('request');

describe('Domain Model', function(){
  var options = { url:'link',
    form:{
      username: 'xxxxx@xxxxxx',
      password: '0000'
    },
    timeout: 2000
  };
  //request auth key
  var promise = new Promise(function(resolve,reject){
    request.post(options, function(error, response, body){
      //body contains the auth key, within a string of JSON
      resolve(body);
    });
  });
  //this is where I'm getting lost
  promise.then(function(data,test){
    var token = (JSON.parse(data))["access_token"];
    console.log(token);
      describe('Initialize',function(){
        it('should be an string', function(){
          (token).should.be.type('string');
        });

      });
  });

});

【问题讨论】:

    标签: javascript promise sails.js mocha.js should.js


    【解决方案1】:

    Mocha 无法找到您的测试,因为您没有正确构建它们。 it() 块应该立即在describe() 块内(并且describe() 块可以在您认为合适的情况下相互嵌套)。如果你想使用 Promise,Promise 应该放在 it() 块内,而不是相反。因此,作为第一遍,更新后的代码应该如下所示(为简洁起见,我删掉了一些部分):

    describe('Domain Model', function(){
      var options = { ... };
    
      describe('Initialize',function(){
        it('should be an string', function(){
          //request auth key
          var promise = new Promise(function(resolve,reject){
            // ...
          });
          promise.then(function(data,test){
            var token = (JSON.parse(data))["access_token"];
            console.log(token);
            (token).should.be.type('string');
          });
        });
      });
    });
    

    请注意,承诺现在包含在 it() 的正文中。 Mocha 现在应该能够找到您的测试。然而,它不会通过。为什么不呢?

    您可能知道,promise 是异步的。使用 mocha 测试异步代码需要使用 done 回调 - 有关更多信息,请参阅 guide。基本上,这意味着it() 块应该接受一个名为done 的参数(名称很重要!)。这个done thingy 是 mocha 自动传入的东西 - 它的存在向 mocha 表明该测试包含异步代码,因此在您说之前它还没有完成执行。表示测试完成的方式是执行done,因为done 实际上是一个回调函数。您应该在您的测试被认为完成的任何时候执行done() - 即,在应该最后运行的任何代码块的末尾,在您的情况下,它是承诺的.then() 处理程序的底部。因此,在我们上一个版本的基础上进行了改进,代码现在看起来像这样(再次,为了简洁,删掉了一些部分):

    describe('Domain Model', function(){
      var options = { ... };
    
      describe('Initialize',function(){
        it('should be an string', function(done){
          //request auth key
          var promise = new Promise(function(resolve,reject){
            // ...
          });
          promise.then(function(data,test){
            var token = (JSON.parse(data))["access_token"];
            console.log(token);
            (token).should.be.type('string');
            done();
          });
        });
      });
    });
    

    注意,我只是将done参数添加到it(),然后在then()的底部调用。

    此时,代码应该可以工作,我认为...我不确定,因为我无法对其进行测试。不过,我们还可以做更多的事情来进一步改进这一点。

    首先,我质疑你在这里使用承诺。如果您有一个用于获取访问令牌的 API,那么我会选择让该 API 返回一个 Promise,因为 Promise 对调用者来说非常方便。但是,我相信您已经注意到,构建 Promise 可能有点乏味,而且我认为它不会为您的代码增加太多价值。我会选择这样做:

    describe('Domain Model', function(){
      var options = { ... };
    
      describe('Initialize',function(){
        it('should be an string', function(done){
          //request auth key
          request.post(options, function(error, response, body){
            //body contains the auth key, within a string of JSON
            var token = (JSON.parse(body))["access_token"];
            console.log(token);
            (token).should.be.type('string');
            done();
          });
        });
      });
    });
    

    这不是更短更甜吗?代码仍然是异步的,所以您仍然应该确保您的 it() 块接受 done 回调,并且您应该在测试完成后调用它。

    现在,如果你仍然坚持使用 Promise,那么我还要警告你一件事。如果您的 .then() 处理程序代码中有错误会发生什么?嗯,照着documentation

    如果被调用的处理程序抛出异常,那么 Promise .then 返回的结果被拒绝。

    你的承诺有拒绝处理程序吗?不,那是什么意思?这意味着错误将被无声无息地吞噬。您的测试将因Error: timeout of 2000ms exceeded 而失败,这是因为从未调用过done 处理程序,但不会显示错误的实际原因,您将费尽心思试图找出问题所在.

    那你能做什么?您可以使用 .then() 的第二个参数来指定拒绝处理程序,并且在那里,您可以利用这样一个事实,即 mocha 传递给您的测试的 done 回调接受错误参数,所以如果您调用 @ 987654354@,你的测试将失败(这就是我们在这种情况下想要的),“某事”将是原因。所以这就是你的情况:

    describe('Domain Model', function(){
      var options = { ... };
    
      describe('Initialize',function(){
        it('should be an string', function(done){
          //request auth key
          var promise = new Promise(function(resolve,reject){
            // ...
          });
          promise.then(function(data){
            var token = (JSON.parse(data))["access_token"];
            console.log(token);
            (token).should.be.type('string');
            done();
          }, function (err) {
            done(err);
          });
        });
      });
    });
    

    但是,我们可以做得更好。考虑如果从拒绝处理程序中抛出错误会发生什么。不太可能,因为你在那里没有做很多事情——只是打电话给done(err)。但如果真的发生了怎么办?嗯,几乎是一样的——错误会被默默地吞掉,测试会失败,出现非特定的timeout 错误,你会再次拔掉头发。有没有办法让这个错误冒泡并被重新抛出?

    事实上,有:Q 和您正在使用的 promise 库都有一个名为 .done() 的备用处理程序(不要与 mocha 的 done 回调混淆)。它类似于.then(),但在未捕获异常方面的行为略有不同。来自documentation

    Promise#done(onFulfilled, onRejected)

    与 .then 的语义相同,只是它不返回承诺 并且重新抛出任何异常以便记录它们(崩溃 非浏览器环境中的应用程序)

    完美,这正是我们想要的。请务必了解什么时候应该使用.then(),什么时候应该使用.done()Q API 在解释方面做得非常出色(并且您使用的 promise 库具有类似的行为 - 我测试过):

    donethen 用法的黄金法则是:要么返回你的承诺 给其他人,或者如果链以您结束,请致电done 终止 它。以catch 终止是不够的,因为catch 处理程序 本身可能会引发错误。

    注意:.catch() 似乎是特定于 Q 的,但它与 onRejected 回调非常相似,它是 .then() 的第二个参数。)。

    因此,考虑到这一点,只需将您的最后一个 .then() 替换为 .done()。当您使用.done() 时,您可以省略拒绝处理程序并依靠承诺库重新抛出任何未处理的期望,因此您将获得错误描述和堆栈跟踪。考虑到上述情况,您的代码现在看起来像:

    describe('Domain Model', function(){
      var options = { ... };
    
      describe('Initialize',function(){
        it('should be an string', function(done){
          //request auth key
          var promise = new Promise(function(resolve,reject){
            // ...
          });
          promise.done(function(data){
            var token = (JSON.parse(data))["access_token"];
            console.log(token);
            (token).should.be.type('string');
            done();
          });
        });
      });
    });
    

    基本上,忽略之前的代码示例,与之前的唯一区别是我们使用.done() 而不是.then()

    希望这涵盖了您入门所需的大部分内容。您可能还需要考虑其他一些事情,例如可能在 before() 挂钩而不是 it() 块中检索身份验证密钥(因为我假设您正在测试的真实内容不是检索密钥- 这只是测试你真正想要测试的东西的先决条件,所以钩子可能更合适 - see here)。我还质疑您是否应该从测试中连接到外部系统,而不是仅仅将其排除(这取决于这些是否是unit tests or integration tests)。而且我相信您可以提出一个更好的断言,即仅检查 token 是一个字符串,例如使用正则表达式来确保它与模式匹配,或者实际测试对受保护资源的请求并确保它穿过去。但我会把这些问题留给你思考。

    【讨论】:

    • 你不再需要function(done)了,在新版本的Mocha中你可以简单地return这个promise,如果promsie解决了它就会通过测试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-29
    • 2016-06-07
    • 1970-01-01
    • 2016-06-16
    • 2016-05-07
    • 2015-01-22
    • 1970-01-01
    相关资源
    最近更新 更多