3

While testing with Mocha I am getting the following error on running server.test.js

1) "before each" hook for "should get all todos":
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

server.test.js

const expect = require('expect');
const request =  require('supertest');

const {app} = require('./../server');
const {Todo} = require('./../todos');


const todos = [
{
    text: 'This is text 1'
},
{
    text: 'This is text 2'
}
];


beforeEach((done) => {

Todo.remove({}).then(() => {
    return Todo.insertMany(todos);
}).then(() => done());
});


describe('GET /todos', () => {
it('should get all todos', (done) => {

    request(app)
        .get('/todos')
        .expect(200)
        .expect(res => {
            expect(res.body.length).toBe(2);
        })
        .end(done);
});
});

But if I do some changes in beforeEach() method like:

updated server.test.js

const expect = require('expect');
const request =  require('supertest');

const {app} = require('./../server');
const {Todo} = require('./../todos');


const todos = [
{
    text: 'This is text 1'
},
{
    text: 'This is text 2'
}
];

beforeEach((done) => {

Todo.remove({}).then(() => {
    Todo.insertMany(todos);
    done();
})
});


describe('GET /todos', () => {
it('should get all todos', (done) => {

    request(app)
        .get('/todos')
        .expect(200)
        .expect(
            expect(res.body.length).toBe(2);
        })
        .end(done);
});
});

Then I am getting no errors. Basically, by chaining promises in beforeEach() method I am running into an error but without that everything is fine. Could anyone explain why is it happening?

server.js

var express = require('express');
var body_parser = require('body-parser');

const {mongoose} = require('./mongoose.js');
const {Todo} = require('./todos');
const {Todo_1} = require('./todos');

var app = express();

app.use(body_parser.json());



//  using GET method
app.get('/todos', (req, res) => {
Todo.find().then((todos) => {
    res.send(todos);
}, (err) => {
    res.status(400).send(err);
});
});

module.exports = {app}

app.listen(3000, () => {
console.log('Server is up on the port 3000');

})
Neeraj Sewani
  • 3,952
  • 6
  • 38
  • 55
  • What if you change arrow function to the old fashioned `function`? Does it help? Mocha is using some context things inside, this can be side affect of it. – libik Jun 18 '18 at 12:59
  • @libik getting the same error after using `function` instead arrow functions – Neeraj Sewani Jun 18 '18 at 13:03

2 Answers2

1

This is incorrect way to handle promises:

Todo.remove({}).then(() => {
    Todo.insertMany(todos);
    done();
})
});

Todo.insertMany is likely asynchronous and returns a promise, and it's detached from promise chain. If there are errors, they will result in unhandled promise rejections, and since tests depend on inserted rows, this will result in race condition:

Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

means exactly what it says. done either wasn't called, or there was a timeout. It is possible for done to never be called because errors aren't handled. It should be .then(done, done).

Since Mocha supports promises, a proper way is:

beforeEach(() => 
  Todo.remove({})
  .then(() => Todo.insertMany(todos))
);

Every promise that appears somewhere in promise chain should be returned.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • I am doing the same in the first code snippet of _server.test .js_. The only difference between the code you suggested and mine is that I am calling _done()_ but I am getting the error whereas in your code I am not. Can u explain why is it happening? – Neeraj Sewani Jun 18 '18 at 17:43
  • As explained in the answer, you should chain promises *and* handle rejections. The code in the answer does that, and your original code doesn't. Returning a promise is straightforward alternative to `.then(done, done)`, and you're not doing that, too. Rejected promise will result in a timeout. – Estus Flask Jun 18 '18 at 18:07
  • Arrow functions without curly braces return whatever is to the right of the arrow. So estus is returning the promise, whereas you are not. Mocha handles a rejected promise returned from a test or hook by failing the test and showing the error. Whereas in your code, if the promise rejects, nothing happens and the test times out. – sripberger Jun 19 '18 at 13:44
  • @estus I've tried your method but still I am getting the same error. – Neeraj Sewani Jun 21 '18 at 11:10
  • though if I increase the timeout like `mocha --timeout 15000` then it's working fine. Can you tell what is the problem? – Neeraj Sewani Jun 21 '18 at 13:02
  • Then it may be really a timeout. One of the operations (likely insert) could be long enough, though I wouldn't expect it from `todos` you've listed. Increase a timeout for all tests or this particular block, see also https://stackoverflow.com/questions/47281828/mocha-change-timeout-for-aftereach . If you still have this problem, consider providing https://stackoverflow.com/help/mcve that can replicate the problem - a repo, etc. – Estus Flask Jun 21 '18 at 13:35
0

instead of putting localhost in the url like this: "mongodb://localhost:27017/yourDbName"

use 127.0.0.1, so it becomes like the following:

"mongodb://127.0.0.1:27017/yourDbName"

I do not know what is the reason behind this solution, but it seems that the server needs some time to process and figure out what the IP of the localhost is.

Hopefully this solution solves your issue.

b1programmer
  • 189
  • 1
  • 5