Here is a workable, reusable way to test an expressjs/mongoose application.

What we’re dealing with

It assumes the following setup; i.e. what every expressjs project could be expected to have:
project
   |- api
       |- app.js
       |- database.js
   |- test
        | test1.test.js
        | test2.test.js
        | ...
Stripped down, your app will probably look something like this:
app.js 
const bodyParser = require("body-parser")
const cors = require("cors")
const express = require("express")

const database = require("./database")

let app = express()

// Pretty standard API app
app
  .use(cors())
  .use(bodyParser.json())
  .use(bodyParser.urlencoded({ extended: true }))

// The article covers this later
database
  .dbconnect()
  .on("error", err => console.log("Connection to database failed."))

// Standard route
app.get("/", async (req, res) => {
    res.send("Hello world")
})  

// other... better stuff here!  

app.listen(5000, () =>
  console.log("Server started at port http://localhost:5000")
)

module.exports = app
Yep! Vanilla expressjs!
The bit to focus on is the line
const database = require(“./database”)
- an import which utilises a nice pattern to handle the Mongo connection implementation. Looking at the module imported will make sense of the
database.dbconnect()
call above:
const mongoose = require("mongoose")

const { MONGODB_URL } = process.env // or some other source of the cnn str

function dbconnect() {
  mongoose.connect(MONGODB_URL, {
    useNewUrlParser: true,
    useCreateIndex: true,
    useUnifiedTopology: true,
    useFindAndModify: false,
  })
  return mongoose.connection
}

function dbclose() {
  return mongoose.disconnect()
}

module.exports = { dbconnect, dbclose }
So that’s where we are. Nothing special… a simple expressjs app running alongside a MongoDB server using the mongoose library.
This article assumes you have an app already; we’re focused on testing it.

The cluttered approach to testing an express API

Let’s imagine how things might look in your tests right now.
test1.test.js
could look something like this:
describe("testing POST /wizard", () => {
  before(async () => {
    //before stuff like setting up the app and mongoose server.
  })
  
  beforeEach(async () => {
    //beforeEach stuff clearing out the db.
  })
  
  after(async () => {
    //after stuff like shutting down the app and mongoose server.
  })

  it("tests wizard creation", done => {
    request(app)
      .post("/wizard")
      .send({ name: "wizard1" })
      .end((err, res) => {
        res.should.be.tested.thoroughly.here
        done()
      })
  })
})
test2.test.js
not dissimilar, with a lot of repetitive garbage:
describe("testing POST /apprentice", () => {
  before(async () => {
    //before stuff like setting up the app and mongoose server.
  })

  beforeEach(async () => {
    //beforeEach stuff clearing out the db.
  })
  
  after(async () => {
    //after stuff like shutting down the app and mongoose server.
  })

  it("tests apprentice creation", done => {
    chai
      .request(app)
      .post("/apprentice")
      .send({ name: "apprentice1" })
      .end((err, res) => {
        res.should.be.tested.thoroughly.here
        done()
      })
  })
})
Repetitive and cluttered: If you agree, the purpose of this article is to look at a way of cleaning that all up.

A good deal clearer

First, we create a module; a single place we can put the repetitive clutter from all our tests. I will call this a “Test Suite”. To separate it visually from the actual tests, I would put it inside a
suites
folder, like so:
project
   |- api
       |- app.js
       |- database.js
   |- tests
        |- test1.test.js
        |- test2.test.js
        |- suites
             |- mochaTestSuite.js
Here is the scaffold of such a suite:
module.exports = (testDescription, testsCallBack) => {
  describe(testDescription, () => {
    before(async () => {
      //before stuff like setting up the app and mongoose server.
    })

    beforeEach(async () => {
      //beforeEach stuff clearing out the db.
    })

    after(async () => {
      //after stuff like shutting down the app and mongoose server.
    })

    testsCallBack()
  })
}
The more eagle-eyed among you will have noticed it isn't much different from the two tests we already have. It has two parameters. A description and a callback.
And this is how we intend to use it in, for example,
test1.test.js
:
// usual require stuff we will cover later
const mochaTestSuite = require("./suites/mochaTestSuite.js")

mochaTestSuite("the test description", () => {
  it("tests wizard creation", done => {
    chai
      .request(app)
      .post("/wizard")
      .send({ name: "wizard1" })
      .end((err, res) => {
        res.should.be.tested.thoroughly.here
        done()
      })
  })
})
The same eagle-eyes will also have noticed we haven't done anything much more than replace the
describe
function (which was originally in test1 and test2) with the new
mochaTestSuite
call — then delete all those before/after calls.
In fact, the two parameters - description and the callback for the tests - work in exactly the same way (in the test suite we’re writing) as they do in the bog standard
describe
function we normally use.

A Finished Test Suite

Rather than laboriously “then add this” and “then do this”… here is, without further preamble, a finished
mochaTestSuite
.
// test/suites/mochaTestSuite.js
const chai = require("chai")
const chaiHttp = require("chai-http")
const mongoose = require("mongoose")
const request = require("supertest")
const { MongoMemoryServer } = require("mongodb-memory-server")
const app = require("../../api/app.js")
const mongooseConnect = require("../../api/database")
const should = chai.should()
chai.use(chaiHttp)

module.exports = (testDescription, testsCallBack) => {
  describe(testDescription, () => {

    // Bonus utility
    const signUpThenLogIn = (credentials, testCallBack) => {
      chai
        .request(app)
        .post("/auth/Thing/signup")
        .send({
          name: "Wizard",
          ...credentials,
        })
        .set("Content-Type", "application/json")
        .set("Accept", "application/json")
        .end((err, res) => {
          chai
            .request(app)
            .post("/auth/Thing/login")
            .send(credentials)
            .set("Content-Type", "application/json")
            .set("Accept", "application/json")
            .end((err, res) => {
              should.not.exist(err)
              res.should.have.status(200)
              res.body.token.should.include("Bearer ")
              testCallBack(res.body.token)
            })
        })
    }

    // Database connector
    const clearDB = () => {
      for (var i in mongoose.connection.collections) {
        mongoose.connection.collections[i].deleteMany(() => {})
      }
    }

    before(async () => {
      let mongoServer = new MongoMemoryServer()
      const mongoUri = await mongoServer.getUri()
      process.env.MONGODB_URL = mongoUri
      await mongooseConnect.dbconnect()
    })

    beforeEach(async () => {
      await clearDB()
    })

    after(async () => {
      await clearDB()
      await mongooseConnect.dbclose()
    })

    // Run the tests inside this module.
    testsCallBack(signUpThenLogIn)
  })
}
A little unpacking:

Usage

Now we can declutter our tests like:
const chai = require("chai")
const chaiHttp = require("chai-http")
const request = require("supertest")
const mongoose = require("mongoose")
const app = require("../api/app.js")
const mochaSuite = require("./suites/mochaSuite")
const should = chai.should()
chai.use(chaiHttp)

mochaTestSuite("testing POST /wizard", signUpThenLogIn => {
  it("creates Wizards", done => {
    chai
      .request(app)
      .post("/wizard")
      .send({ name: "wizard1" })
      .end((err, res) => {
        res.body.name.should.equal("wizard1")
        res.should.be.thoroughly.tested.in.other.ways
        done()
      })
  })
})
Sweet. With all the before and after setup being handled by the suite, we can focus on testing functionality.
The
mochaTestSuite
has two parameters, the description and the callback function - which will (duh!) callback to run the actual tests inside
itself, having setup the app and mongoose servers for them.
And that’s it really.
But I promised to show you how the
signUpAndLogInfunction
would work. Some of your requests may need to be authenticated and it helps to outsource that clutter as well.
In fact, it works in a similar way, except
signUpAndLoginIn
’s callback parameter will be inside one of your it tests.
// requires as normal
const mochaSuite = require("./suites/mochaSuite")

mochaTestSuite("testing POST /apprentice", signUpThenLogIn => {
  it("Logs in Wizard can create Apprentices", done => {
    signUpThenLogIn(
      { username: "grandwizard", password: "IShallPass" },
      token => {
        chai
          .request(app)
          .post("/apprentice")
          .send({ name: "apprentice1" })
          // use the token
          .set("Authorization", token)
          .end((err, res) => {
            res.body.name.should.equal("apprentice1")
            res.should.be.thoroughly.tested.in.other.ways
            done()
          })
      }
    )
  })
})
So…
This also shows you how other features can be added to the test suite to
“outsource” the laborious, repetitive code that tests need just to get
them prepped for actually running.
Cleaner tests not only help with debugging but help other developers who might be looking at your tests to understand how to use your code.

Core, Thanks!