0

I want to unit test a function. In that function I'm using Co with a generator function. When an error occurs I catch it, and I call cb with the error

In my unit test I make a false assertion but mocha doesn't report it, it just times out:

//function to test

function start(data, cb) {
  co(function * coStart() {
    yield Promise.reject('err'); // yield error for sake of demo
  }).then(function(result){
    return cb(null, result);
  }).catch(function (err) {
    // we get here
    return cb(err);
  });
}

// mocha test

it('fails on incorrect data', function(done){
  MyModel.start({'foo': 'bar'}, function(err, res){
    assert.equal(err, 'err2'); //this fails but mocha just stops here and times out
    done();
  });
});

Clearly I'm doing something wrong here?

I know you can return a promise to mocha and omit the done-callback in the test, but my function 'start' cannot return a promise, its like middleware so it should work with a callback

Ernie
  • 972
  • 1
  • 10
  • 23

1 Answers1

1

Your code does something similar to this:

Promise.reject('err')
       .catch(() => {
         // throw another exception
         throw 'foo';
       });

That is: within the .catch() clause, another exception is thrown (in your case, the exception thrown by assert.equal(err, 'err2')), synchronously, which isn't handled (by, for instance, another .catch() clause). This will cause the second exception to be ignored (see this answer for an explanation), but it will stop the test case from finishing (the done() line is never reached, hence timing out the test case).

If you really need callback support, you can work around this by either adding another .catch() clause in start(), or by calling the callbacks asynchronously:

return setImmediate(function() { cb(null, result) });
...
return setImmediate(function() { cb(err) });
...

But ideally, you should consider the possibility of removing callback support entirely and just passing around the promises. Mocha supports promises out of the box, so the code would look something like this:

function start(data) {
  return co(function * coStart() {
    yield Promise.reject('err');
  });
}

it('fails on incorrect data', function(){
  return start({'foo': 'bar'}).then(function() {
    throw new Error('should not be reached');
  }, function(err) {
    assert.equal(err, 'err2');
  });
});
Community
  • 1
  • 1
robertklep
  • 198,204
  • 35
  • 394
  • 381
  • Thx man, I finally get this now! setImmediate works. Are there any implications on using this, cause it feels a bit like hacking the 'normal flow' – Ernie May 19 '16 at 10:06
  • 1
    There are some alternatives like [`dezalgo`](https://github.com/npm/dezalgo) that you could use instead, which may give some cleaner code. I would prefer a "full promise" solution myself, but I realize that's not always an option :D – robertklep May 19 '16 at 10:53
  • Thx! I'll check it out – Ernie May 19 '16 at 10:58