0

I’m learning to create RestAPI’s using NodeJS and MongoDB from this tutorial, and from the repo they have provided[which has quite a lot of updates from the post], I further modified it to do UnitTesting of my Mongo server with Jest and SuperTest.

The major changes I’ve made to the repo before adding mine is:

Move the listen logic to a start.js, while in package.json make this the main file

start.js

const { app } = require("./index");

// Setup server port
var port = process.env.PORT || 8080;

app.listen(port, function () {
    console.log("Running RestHub on port " + port);
});

module.exports = {app};

wrap the app in the index.js by an export after moving the logic.

index.js

app.use('/api', apiRoutes);
// Launch app to listen to specified port
// app.listen(port, function () {
//     console.log("Running RestHub on port " + port);
// });

module.exports = {app};

package.json

{
  "name": "resthub2",
  "version": "2.0.0",
  "description": "A Node App demonstrating simple RESTFul API implementation",
  "main": "start.js",
  "scripts": {
    "test": "jest",
    "start": "node start.js"
  },
  "keywords": [
    "API",
    "resful",
    "json",
    "node",
    "mongodb",
    "express"
  ],
  "author": "David Inyang-Etoh",
  "license": "ISC",
  "dependencies": {
    "@shelf/jest-mongodb": "^1.2.3",
    "body-parser": "^1.19.0",
    "express": "^4.17.1",
    "jest": "^26.4.2",
    "mongoose": "^5.6.4",
    "supertest": "^5.0.0"
  }
}

I followed the special instructions to connect Jest and SuperTest to MongoDB and added these 2 files.

jest.config.js

module.exports = {
  "preset": "@shelf/jest-mongodb"
};

contactController.test.js

const {MongoClient} = require('mongodb');
const {app} = require('./index'); // Link to your server file

const supertest = require('supertest');
const request = supertest(app);
const mongoose = require("mongoose");
const Contact = require("./contactModel");



describe('insert', () => {

  let connection;
  let db;

  beforeAll(async (done) => {
    connection = await MongoClient.connect(global.__MONGO_URI__, {
      useNewUrlParser: true,
    });
    db = await connection.db(global.__MONGO_DB_NAME__);
    Contact.deleteMany({}, (err) => {
      done();
    });

  });

  afterAll((done) => {

    console.log("After all?");
    // await connection.close();
    mongoose.connection.close();
    // await db.close();
    done();
  });

  it('should insert a doc into collection', async (done) => {

    try {
      const item = {};
      item["name"] = "John 3";
      item["email"] = "john@example.org";
      item["phone"] = "12345678";
      item["gender"] = "Male";

      const response2 = await request
        .post('/api/contacts')
        .send(item);
      console.log(response2.status);
      console.log("Response2 done");


      const response = await request.get("/api/contacts");
      console.log(`Weird response status is ${response.status}`);
      expect(response.status).toBe(200);
      expect(response.body.data.length).toBe(1);

      done();

    } catch (error) {
      console.log(error);
    }
  }, 30000);
});

However, my tests do not terminate even though I would pass all(which is just 1), and gives me the following message

Jest did not exit one second after the test run has completed. This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

Have tried most of the suggestions in here such as runInBand, closing the connection with mongoose instead, but it is still not terminating. Does anyone have a fix to my issue?

Prashin Jeevaganth
  • 1,223
  • 1
  • 18
  • 42
  • You create **Mongo* connection but close **Mongoose** connection. And Mongo `db` connection isn't used for anything. – Estus Flask Sep 27 '20 at 13:02
  • @EstusFlask Sorry for the redundant code there. I've actually tried closing Mongo* connection using `connection.close()` in the comments along with the old async signature. That doesn't fix the problem too. I agree with the `db`, was supposed to clean that up bc I can't get the db to clean up before my tests had I used that instead of `Contact` import. Do you see a fix for my situation? – Prashin Jeevaganth Sep 27 '20 at 13:09
  • I'm not sure what you mean, you need to close the same connection that is used by tested units (which aren't shown). That you mix up promises and callback doesn't help the situation. Do not mix `done` with `async`. Mongo(ose) is promise-based and can works with `async`. If there are two connections (Mongoose default and Mongo `db`), they should be both closed, `await mongoose.connection.close(); await db.close()`. – Estus Flask Sep 27 '20 at 13:19
  • This should be enough if there are no other handlers (like server connection). If Mongoose uses non-default connection, it needs to be exposed and closed. Basically every connection that was opened needs to be closed at the end of `afterAll` (Supertest closes its own connection automatically, it doesn't need to be handled). And again, `db` is useless in your code and should be eliminated. In case there is still a problem, please, provide https://stackoverflow.com/help/mcve for your problem in order for it to be understandable. – Estus Flask Sep 27 '20 at 13:20
  • Actually db.close() isn't even a function according to them, but deleting any traces of db seems to solve the problem. Do you want to make this an answer? – Prashin Jeevaganth Sep 27 '20 at 13:25
  • Yes, it's not, needs to be `connection.close`. Posted an answer for clarity. – Estus Flask Sep 27 '20 at 13:46

1 Answers1

1

This error shouldn't occur if there are no ongoing asynchronous operations and no open handlers like database connections and server listeners. This means that every connection needs to be closed at the end of afterAll. Closing a connection is asynchronous operation and needs to be awaited.

Since start.js isn't imported, the server doesn't need to be closed, Supertest request(app) sets up a server and closes it automatically.

Mongo and Mongoose APIs support promises and can be handled with async..await. async shouldn't be mixed with done because this is an antipattern that commonly results in incorrect control flow.

If there is default mongoose.connect connection, it needs to closed:

  afterAll(async () => {
    await mongoose.connection.close();
  });

If there are non-default mongoose.createConnection connections, they need to be exposed and closed as well.

Mongo connection isn't usable here because it differs from Mongoose connection. Existing Mongoose default connection can be accessed for Mongo operations like a cleanup. In case there's a need for Mongo (not Mongoose) connection, it needs to be explicitly closed:

  afterAll(async () => {
    await connection.close();
  });

This can be done in separate afterAll blocks in order for failed operations to not affect others.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565