将 mocha+chai 与 co 一起使用

Using mocha+chai together with co

chai.expect 断言失败时, 他们通常没有通过测试 负面结果被添加到报告中 对于测试运行程序(在本例中为 mocha)。

但是,当我使用使用 co.wrap() 包装的生成器函数时, 如下所示, 奇怪的事情发生了: 当断言通过时,一切运行正常。 然而,当断言失败时,测试超时。

co如何与mocha+chai一起使用?


it('calls API and then verifies database contents', function(done) {
  var input = {
    id: 'foo',
    number: 123,
  };
  request
    .post('/foo')
    .send(input)
    .expect(201)
    .expect({
      id: input.id,
      number: input.number,
    })
    .end(function(err) {
      if (!!err) {
        return done(err);
      }

      // Now check that database contents are correct
      co.wrap(function *() {
        var dbFoo = yield foos.findOne({
          id: input.id,
        });
        continueTest(dbFoo);
      })();

      function continueTest(dbFoo) {
        //NOTE when these assertions fail, test times out
        expect(dbFoo).to.have.property('id').to.equal(input.id);
        expect(dbFoo).to.have.property('number').to.equal(input.number);
        done();
      }
    });
});

解决方案:

问题的出现是由于 co.wrap() 吞噬了 expect() 抛出的异常,不允许它冒泡到 mocha 找到它所需的位置,正如所指出的@Bergi 下面。

解决方案是使用 co() 而不是 co.wrap(),然后添加 .catch() 并传递 done 回调,如下所示。

      // Now check that database contents are correct
      co(function *() {
        var dbFoo = yield foos.findOne({
          id: input.id,
        });
        continueTest(dbFoo);
      }).catch(done);

co.wrap 从生成器中捕获异常,并拒绝返回的承诺。它 "swallows" 从 continueTest 中的断言中抛出的错误。顺便说一句,您可以直接调用 co(…).

而不是使用 .wrap 并立即调用它
co(function*() {
    …
}).then(done, done); // fulfills with undefined or rejects with error

co(function*() {
    …
    done();
}).catch(done);

顺便说一句,要正确使用 co,您需要将所有异步函数放在一个生成器中:

it('calls API and then verifies database contents', function(done) {
  co(function*() {
    var input = {
      id: 'foo',
      number: 123,
    };
    yield request
      .post('/foo')
      .send(input)
      .expect(201)
      .expect({
        id: input.id,
        number: input.number,
      })
      .endAsync(); // assuming you've promisified it

    // Now check that database contents are correct
    var dbFoo = yield foos.findOne({
      id: input.id,
    });

    expect(dbFoo).to.have.property('id').to.equal(input.id);
    expect(dbFoo).to.have.property('number').to.equal(input.number);

  }).then(done, done);
});

此处代码的根本问题是您试图在 suptertest 的 end CPS 回调中屈服;由于此函数不是生成器函数 yield 无法使用,您的异常将消失在以太中,如您所见。

直接使用 co.wrap 是正确的方式(当使用 co 时)给 mocha 一个承诺,它可以用来跟踪使用生成器函数和 yield 的测试的成功或失败异步流控制,你只需要序列化你的测试,以便数据库检查在超级测试之后运行。

您的解决方案通过使用 co 将生成器函数转换为承诺来解决此问题,然后使用该承诺 "convert" 通过其 catch 返回到摩卡的 CPS 样式异步函数调用 done 如果数据库检查抛出:

co(function *() {
    var dbFoo = yield foos.findOne({
      id: input.id,
    });
    continueTest(dbFoo);
}).catch(done);

拯救的承诺

好消息是 supertest 也支持 promises,这可以更简单地完成。

您缺少的重要部分(如 Bergi 的回答中所示)是承诺、生成器函数,很快 async/await 可以协同工作。在这种情况下我们可以利用这个在生成器函数内部直接产生一个promise,supertest的promise。

这会直接在该测试生成器函数中保留数据库检查,其中任何异常都将由 co.wrap 正确处理并作为拒绝传递给 mocha。

测试现在已经整齐地序列化,没有任何 CPS 碎片。这难道不是 js 中这些新异步功能的真正承诺吗?


it('calls API and then verifies database contents', co.wrap(function*() {
  var input = {
    id: 'foo',
    number: 123,
  };

  yield request
    .post('/foo')
    .send(input)
    .expect(201)
    .expect({
      id: input.id,
      number: input.number,
    });

  // Now check that database contents are correct
  var dbFoo = yield foos.findOne({
    id: input.id,
  });

  expect(dbFoo).to.have.property('id').to.equal(input.id);
  expect(dbFoo).to.have.property('number').to.equal(input.number);  
}));