javascriptbddecmascript-6assertionco

Using mocha+chai together with co


When chai.expect assertions fail, they normally fail the test and the negative result gets added to the report for the test runner (in this case mocha).

However, when I use a generator function wrapped using co.wrap(), as seen below, something strange happens: when the assertions pass, everything runs just fine. When the assertions fail, however, the test times out.

How can co be used together with 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();
      }
    });
});

Solution:

The problem arose due to co.wrap() swallowing the exception thrown by expect(), not allowing it bubble up to where it needed to for mocha to find it, as pointed out by @Bergi below.

The solution was to use co() instead of co.wrap(), and add .catch() and pass that the done callback, as seen below.

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

Solution

  • co.wrap catches exceptions from the generator, and rejects the returned promise. It "swallows" the error that is thrown from the assertions in continueTest. Btw, instead of using .wrap and immediately calling it, you can just call co(…).

    co(function*() {
        …
    }).then(done, done); // fulfills with undefined or rejects with error
    

    or

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

    Btw, to use co properly you'd put all your asynchronous functions in a single generator:

    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);
    });