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?
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.