Using mocha+chai together with co

1.1k Views Asked by At

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

There are 2 best solutions below

2
On BEST ANSWER

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

The root issue with your code here is that you're trying to yield in suptertest's end CPS callback; since this function is not a generator function yield can not be used and your exceptions will disappear into the ether, as you've seen.

Using co.wrap directly is the proper way (when using co) to give mocha a promise that it can use to track the success or failure of a test that uses generator functions and yield for async flow control, you just need to serialize your test so that the db check runs after supertest.

Your solution solves this by using co to convert the generator function to a promise, and then uses that promise to "convert" back to mocha's CPS style async by using its catch function to call done if the db check throws:

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

Promises to the rescue

The good news is that supertest also supports promises and this can be done much more simply.

The important part (like shown in Bergi's answer) that you're missing is that promises, generator functions, and soon async/await, can work together. In this case we can take advantage of this to directly yield a promise, supertest's promise, inside the generator function.

This keeps the db check directly in that test generator function where any exceptions will be handled properly by co.wrap and passed as a rejection to mocha.

The test is now neatly serialized without any CPS detritus. And isn't that what the promise of these new async features in js are really about?


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