1

I'm using something akin to How to properly reuse connection to Mongodb across NodeJs application and modules to keep my mongoDB Connection open.
This requires all the code to go into a MongoDB.connectDB(async (err) => {...} Block.
How would I use this when writing Tests with Mocha.
Do I have to use a separate Connection for every test? Like this?

const MongoDB = require('../src/mongoUtil') // providing access to the mongo database
var events = require('../src/events') // containing all my database Functions

describe('events.js', function () {
  describe('addEvent()', function () {
    it('should return the event, when added succesfully', async function () {
      await MongoDB.connectDB(async (err) => {
        if (err) throw err

        const database = MongoDB.getDB()
        const eventsCollection = database.db().collection('events')

        const event = {
          name: 'test Event',
          members: []
        }

        const result = await events.addEvent(eventsCollection, event)

        MongoDB.disconnectDB()

        if (result.name !== event.name) {
          return new Error('TODO')
        }
      })
    })
  })
})

For this example to work properly I probably also have to manually set the test to done, if I understood the mocha website correctly?
Can I have just one connection?

mongoUtil.js

const MongoClient = require('mongodb').MongoClient
const uri = 'mongodb://localhost:27017/testing'

let _db

const connectDB = async (callback) => {
  try {
    MongoClient.connect(uri, { useNewUrlParser: true }, (err, db) => {
      _db = db
      return callback(err)
    })
  } catch (e) {
    throw e
  }
}

const getDB = () => _db

const disconnectDB = () => _db.close()

module.exports = { connectDB, getDB, disconnectDB }

events.js

const addEvent = async (collection, event) => {
  try {
    const exists = await collection.findOne({ 'name': event.name })
    if (exists) {
      return false
    } else {
      const results = await collection.insertOne(event)
      return results.ops[0]
    }
  } catch (e) {
    throw e
  }
}
module.exports = { addEvent }
Julian Veerkamp
  • 1,694
  • 2
  • 16
  • 20

1 Answers1

2

You don't need to put everything inside MongoDB.connectDB({...}). This is called callback hell which should be avoided when possible. mongoUtil is inconvenient wrapper that doesn't make MongoDB API simpler or easier to use.

await MongoDB.connectDB(async (err) => { ... is a mistake. Putting async function as a callback is not enough and will result in improper control flow.

MongoClient.connect uses error-first callbacks, so it can be used with done to pass errors to test suite:

let db;

beforeEach(done => {
  MongoClient.connect(uri, { useNewUrlParser: true }, (err, _db) => {
    db = _db;
    done(err);
  });
});

connectDB incorrectly promisifies MongoClient.connect. There's no need for one-time callbacks when promises and async..await are in use. It could be:

const connectDB = () => {
  return new Promise((resolve, reject) => {
    MongoClient.connect(uri, { useNewUrlParser: true }, (err, db) => {
      if (err) reject(err);
      else resolve(db);
    });
  });
};

But the fact is that mongodb supports promises natively when callback argument is omitted. mongoUtil needs only one helper function, connectDB:

const connectDB = () => MongoClient.connect(uri, { useNewUrlParser: true });

Which can be used with Mocha seamlessly since it supports promises:

let db;

beforeEach(async () => {
  db = await connectDB();
});

afterEach(() => db.close());

close is asynchronous and returns a promise, too.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • This solved my issue, thank you. If I want to use the same solution in app.js, I'd need to wrap it into an async function and use await (like your beforeEach example) or use .then(). Could I just create a `const main = async () => {}` function? – Julian Veerkamp Aug 24 '18 at 15:14
  • 1
    You're welcome. A common recipe for app entry point now is async IIFE, `(async () => { ... })().catch(console.error)`. – Estus Flask Aug 24 '18 at 15:29
  • Hi guys - I am in a similar situation and the solution you are discussing here seems to be closest one to what I want. However, I am having trouble getting it to work. Is it possible to provide a small example that shows both the code in the app.js file and in the test file? I am unable to get the test to exit, since it is not closing the Mongo connection and I don't know how to get the test to close it once it finishes running. I posted about this here - https://stackoverflow.com/questions/63759862/closing-a-mongodb-connection-after-running-mocha-tests-for-an-express-applicatio. – Muthu Palanisamy Sep 06 '20 at 01:33
  • I am all set. Updated my question with the right answer. – Muthu Palanisamy Sep 06 '20 at 18:40