0

I'm presently using mocha 2.5.3, supertest 2.0.0, knex 0.11.10, restify 4.1.1 and sqlite3 3.1.4.

I have the following very simple restify server:

const restify = require('restify');
const knex = require('knex')({
  client: 'sqlite3',
  connection: {
    'filename': 'test.db'
  }
});

const app = restify.createServer();

app.get('/', (req, res, next) => {
  knex.select().from('nonexistent_table')
  .then((rows) => {
    return res.json(rows);
  })
  .catch((err) => {
    return res.send('error');
  });
});
module.exports = app;

The below test will cause the test to timeout at 2000ms instead of failing:

const assert = require('assert');
const supertest = require('supertest');
const app = require('./app');

describe('GET /', function () {

  it('should not timeout', function (done) {
    supertest(app)
    .get('/')
    .end(function(err, res) {
      assert(false);
      done();
    });
  });
});

If the call to knex is fulfilled instead of being rejected, the test fails properly and does not timeout. The timeout appears to occur only if the knex call is rejected.

Does anyone have thoughts on what could be causing the timeout instead of a proper failure?

EDIT: I've debugged this as far as I could, and it seems the timeout happens when mocha tries to generate a stacktrace.

  • See my answer [here](http://stackoverflow.com/a/39103469/893780): because the assertion throws an error, `done` is never called _and_ the error is never caught. – robertklep Aug 25 '16 at 20:13
  • 1
    @robertklep thanks, but I'm not sure that is the correct answer for two reasons. First, if knex resolves, the test fails normally. Since I'm doing a straight `assert(false)` in the test, the mode of failure should not depend at all on the response from restify server or the result from knex, but somehow it does! Second, if I write a mocha test that does not do an async http call and simply throw an error, the test will rightfully fail and not timeout, even though I never call `done()`. This leads me to believe that throwing errors /failing assertions is the normal way that mocha tests fail. –  Aug 25 '16 at 20:28

2 Answers2

1

supertest uses superagent behind the scenes and superagent supports promises. (Search for .then here.) So you can use .then instead of .end and just return the promise instead of using done:

  it('should not timeout', function () {
      return supertest(app)
          .get('/')
          .then(function(res) {
              assert(false);
          });
  });

When I use the above code with the rest of your code in the question, then I get a proper failure.

As to why using done did not work, it is not clear to me. It may be that supertest or superagent swallows exceptions that are raised in the callback passed to .end(). If exceptions are swallowed, then Mocha cannot detect them. You would then have to catch the exception raised by the failing assertion yourself and pass them to done. I prefer to use promises.

Louis
  • 146,715
  • 28
  • 274
  • 320
  • My guess is that the `end` callback, in the failing case, is called from _inside_ the `.catch()` in the route handler, which will swallow the assertion error _and_ prevent `done` from getting called. – robertklep Aug 25 '16 at 20:39
  • I agree promises may be the right direction to go here. But I'm starting to think this may be a bug in node, since if I raise an exception inside the `.end()` callback, but knex is not called on the server side, the test fails normally. That says to me that superagent is not swallowing exceptions. Something else is going on here, and I think it has to do with mocha's generation of a stacktrace. I will update my question with my findings on that. –  Aug 25 '16 at 20:43
  • Another theory is that because the `catch` doesn't send an error response, some internal `superagent` promise may get resolved so the subsequent assertion failure cannot reject it anymore. – robertklep Aug 25 '16 at 20:46
  • @robertklep I think you may be on to something here. I tried swapping out restify for express and it worked just fine, which says to me there is something in the way restify interacts with superagent that is causing the exception to get stuck in the `.catch()` route. Maybe this is a bug in restify. –  Aug 25 '16 at 20:47
  • @harrymonster yeah I was just about to post the same, Express works as expected =D – robertklep Aug 25 '16 at 20:49
0

try to change the first line with: bluebird.resolve(knex.select().from('nonexistent_table'))

also you need to require 'bluebird' on top. It'll resolve an issue an give you some context about an error

I think the problem with select: you forget an argument

kharandziuk
  • 12,020
  • 17
  • 63
  • 121
  • Just tried both of those potential solutions and they did not fix the problem. I.e., I tried `knex.select('*').from('nonexistent_table')` and I also tried `bluebird.resolve(knex.select('*')from('nonexistent_table'))`. –  Aug 25 '16 at 20:19