MongoDB connections do not close in test scenarios - node.js

I am using express and connect via the npm package "mongodb": "^3.0.10" to a mongodb.
My app.ts looks like this:
const app = express();
let server: http.Server;
(async () => {
app.use(bodyParser.json());
let db: Db;
try {
const host = config.get<string>("api.dbConfig.host");
console.log(host);
const dbName = config.get<string>("api.dbConfig.dbName");
const port = config.get<string>("api.dbConfig.port");
const connectionString = "mongodb://" + host + ":" + port;
const mongoClient = await MongoClient.connect(connectionString);
db = mongoClient.db(dbName);
await db.collection("users").createIndex({email: 1}, {unique: true});
}
catch (err) {
console.log(err);
console.log("can not connect to MongoDB");
}
const userRepo = new UserRepository(db);
// Routes
app.use("/v1/users", userRoutes(userRepo));
server = http.createServer(app);
server.listen(3000);
server.on("listening", () => {
console.log("listening");
});
})();
module.exports = app;
For testing i use jest and supertest. The tests run successfully, but they never end, because there are still connections to mongodb.
The tests look something like this:
describe("user routes", function () {
it("should return all users", async () => {
const response = await agent(app).get("/v1/users/");
expect(response.status).to.be.equal(200);
expect(response.body).to.be.an("array");
expect(response.body).to.have.lengthOf(2);
});
I understand, that the mongodb driver uses connection pooling and the way i pass the db- (or collection-) object to my user repository, makes it impossible to close the connections manually in a test scenario.
I guess a need a better way a pass the db connection to my user repository, but i can not think of a better, or more decoupled way at the moment.

Try await mongoClient.close() after your tests are done. See MongoDB docs. As far as I know, Jest supports before() and after() hooks, and I imagine before() and after() hooks support async/await like Mocha's do.

Related

can't read properties for SET/GET in redis and node.js

I just start to explore redis. I want to cache some data using redis. I set up redis connection in the server.ts file and export it from there. Import it in my controller function and try to use set and get but this error comes for both get and set.
TypeError: Cannot read properties of undefined (reading 'get')
//sever.js---> redis connection part
export const client = redis.createClient({
url: "redis://127.0.0.1:6379",
});
client.connect();
client.on("error", (err) => console.log("Redis Client Error", err));
const app: Application = express();
//controller
import { client } from "../server";
const allProjects = async (req: Request, res: Response): Promise<void> => {
const cachedProjects = await client.get("projects");
if (cachedProjects) {
res.status(200).json(JSON.parse(cachedProjects));
}
const projects = await Projects.find({});
if (!projects) {
res.status(400).send("No projects found");
throw new Error("No projects found");
}
await client.set("projects", JSON.stringify(projects));
res.status(200).json(projects);
};
My Redis server is running and I can use set/get using redis cli. I make a mistake somewhere but can't find it.
I am using Node.js, Express.js and Typescript
This error is most likely because client is undefined. This suggests that your import from server.js isn't doing what you think it is. This could be because server.js is special from a Node.js point of view as it's the default file that loads when you run npm start. Might be better to put that code in its own file.
To test this, try doing a .get and .set in server.js after your connection is established and see if that works. If so, you've proved you can talk to Redis. The rest is debugging.
Also, you might want to refer to the example code on the Node Redis Github repo. I've added it here for your convenience:
import { createClient } from 'redis';
const client = createClient();
client.on('error', (err) => console.log('Redis Client Error', err));
await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
Note that Alexey is right that you need to await the establishment of a connection. You should also add the error handler before doing so. That way if establishing a connection fails, you'll know about it.
Wait until client connected to the server and then export it
//sever.js---> redis connection part
const client = await redis.createClient({
url: "redis://127.0.0.1:6379",
});
await client.connect();
client.on("error", (err) => console.log("Redis Client Error", err));
const app: Application = express();
export client
I find the solution. Actually, I write the Redis server connection code in the wrong place. It should be after all path. Then it works fine.
app.use("/api/v1/uploads", imageUpload);
app.use("/api/v1/forgetPassword", forgetPassword);
app.use("/api/v1/resetPassword", resetPassword);
app.use("/uploads", express.static(path.join(dirname, "/uploads")));
client.connect();
client.on("connected", ()=> console.log("Redis connected"))
client.on("error", (err) => console.log("Redis Client Error", err));
app.use(notFound);
var server = app.listen(process.env.PORT || 3001, () =>
console.log(`Listening on port ${process.env.PORT}`)
);

Connection Not established in Mocha to MongoDB Atlas

When starting my application it connects to MongoDB Atlas as logged, however, when running mocha tests it does not even try to connect to the DB.
here is my server.js file
require('dotenv').config()
const express = require('express');
const connectDB = require('./DB/connection')
const app = express();
app.use(express.json())
const PORT = process.env.PORT
connectDB();
app.listen(PORT, () => console.log(`Server started at ${PORT}`))
and this is the connection..js file
const mongoose = require('mongoose');
const URI = `mongodb+srv://${process.env.DB_USERNAME}:${process.env.DB_PASSWORD}#betacluster.7jf4v.mongodb.net/servicenowclone?retryWrites=true&w=majority`
const connectDB = async () => {
try {
mongoose.connect(URI, {
useUnifiedTopology: true,
useNewUrlParser: true
});
mongoose.connection.once('open',function() {
console.log('connection established');
}).on('error',() => console.log('gi atay way connection sa database man'))
} catch (err) {
console.log(err)
}
}
which logs
Server started at 3000
connection established
so I know it connects to the DB successfully, however when creating tests using mocha, it doesn't even try to connect to the DB, here is the complete test file.
const mocha = require('mocha');
const assert = require('assert');
const ticketInstance = require('../models/ticket')
//describe tests
describe('saving a ticket', function () {
it('create ticket', async function (done) {
const newTicket = new ticketInstance({
number: 1,
type: 'Request',
customer: 'Carlo Principe',
description: 'first ticket created from a test',
subject:'test subject'
})
newTicket.save().then(function (){
assert(newTicket.isNew === false);
done()
})
});
})
Am I missing something, it logs timeout exceeded and does not show the connection established console.log I created in connection.js
Thanks!
The best way is mocha --delayed switch. mocha doc says
If you need to perform asynchronous operations before any of your suites are run (e.g., for dynamically generating tests), you may delay the root suite. Run mocha with the --delay flag.
For example, use mocha in this way mocha --recursive --exit --delay --ui tdd tests.js and --delayed enable you to trigger running the root suite via calling run() explicitly.
const fn = async x => {
return new Promise(resolve => {
setTimeout(resolve, 1500, 2 * x);
});
};
(async function() {
const z = await fn(3);
suite("won't run until run() executes", () => {})
run();
})();
For more information, please read https://mochajs.org/#delayed-root-suite.

Closing a MongoDB connection after running Mocha tests for an Express application

I have an Express application that looks like this.
const app = express();
...
...
...
router.post(...);
router.get(...);
router.delete(...);
app.use('/api/v1', router);
MongoClient.connect(mongoUri, { useNewUrlParser: true })
.then(client => {
const db = client.db('db_name');
const collection = db.collection('collection_name');
app.locals.collection = collection;
})
.catch(error => console.error(error));
const server = app.listen(settings.APIServerPort, () => console.log(`Server is listening on port ${settings.APIServerPort}.`));
module.exports = {
server,
knex // using this to connect to the RDBMS
}
The application uses both an RDBMS and Mongo.
I wrote tests for the application using Mocha and added the following block to the Mocha test.
const app = require('../app');
...test 1...
...test 2...
...test 3...
...
...
...
...test n...
after(async () => {
await app.knex.destroy();
});
The after hook closes out my connection to the RDBMS.
However, I don't know how to close the MongoDB connection once the test finishes.
Owing to keeping this connection open, the test never exits and hangs once all the tests have been run.
The closest answer that I have been able to find is this one - Keep MongoDB connection open while running tests using mocha framework.
However, I was unable to get to work for me.
Can someone please help with this?
Update
A combination of the answers below is what solved the problem.
const mongoClient = new MongoClient(mongoUri, { useNewUrlParser: true });
mongoClient.connect()
.then(client => {
const db = client.db('...');
const collection = db.collection('...');
app.locals.collection = collection;
})
.catch(error => console.error(error));
const server = app.listen(settings.APIServerPort, () => console.log(`Server is listening on port ${settings.APIServerPort}.`));
module.exports = {
server,
knex,
mongoClient
}
We can rewrite the mongo function to make it work
const client = new MongoClient(uri);
client.connect()
.then(client => {
const db = client.db('db_name');
const collection = db.collection('collection_name');
app.locals.collection = collection;
})
.catch(error => console.error(error));
And in the after block -
after(async () => {
await app.knex.destroy();
await client.close();
});

when to use `mongoose.disconnect()` or `db.close()`

I am new to MongoDB. Now I am using Mongoose to take care of the database in my express.js app. My question is that I don't know when should I close the connection? Since someone says better to close it, while someone else says to leave it open? And what is the difference between db.close() and mongoose.disconnect()? What is your experience of using these?
Thanks!
Open a connection and share that between your routes/services. You can initialize the db and all related schema before you start express.
import mongoose from 'mongoose';
// define models
import User from './user';
import Order from './order';
// connect to db
const connect = async () => {
await mongoose.connect(process.env.DATABASE_URL);
return mongoose.connection;
};
const models = { User, Order };
export { connect };
export default models;
Then connect before you startup Express:
import models, { connect } from './models';
...
// connect to the db
const connection = await connect();
// now pass in the models to your routes
request('./routes/users')(models);
request('./routes/orders')(models);
// or use middleware so you can access in routes like req.models.user.find({})
app.use((req, res, next) => {
req.models = models;
next();
})
app.listen(process.env.PORT, () =>
console.log(`Example app listening on port ${process.env.PORT}!`),
);
You can add a cleanup handler to listen to process.exit and close the connection there. Roughly something like:
const cleanUp = (eventType) => {
connection.close(() => {
console.info('closed');
});
};
[`exit`, `SIGINT`, `SIGUSR1`, `SIGUSR2`, `uncaughtException`, `SIGTERM`].forEach((eventType) => {
process.on(eventType, cleanUp.bind(null, eventType));
})

How to connect to a MongoDB database from an application deployed on ZEIT Now?

I have deployed an application on Zeit Now using ExpressJS. The application makes a connection to MongoDB using Mongoose. However, the state of the connection, which I obtain using mongoose.connection.readyState is displayed as 2, which denotes 'connecting'.
I tried running the application locally and it works fine, where I am able to write to the database.
const connectionURL = "mongodb+srv://MONGODB_USERNAME:MONGOD_BPASSWORD#cluster1-23abc.mongodb.net/DATABASE_NAME?retryWrites=true"
expressApp.listen(3000, function() {
console.log("Listening to port 3000");
});
mongoose
.connect(
connectionURL, { useNewUrlParser: true }
)
.then(function() {
console.log("db connected!");
});
expressApp.get("/", function(req, res) {
res.write(`Connection State: ${mongoose.connection.readyState}\n`);
res.end();
});
I would expect mongoose.connection.readyState to be 1, which denotes 'connected'.
However, mongoose.connection.readyState is stuck at 2, which denotes 'connecting'.
Also, now logs does not show any errors.
You'll want to cache your MongoDB connection so you won't have to make a new connection on each lamda call.
You can make an lib folder and empty mongoose.js file (lib/mongoose.js) as I did and place this code inside:
`import mongoose from 'mongoose';
let cachedDb = null;
console.log('outside-cachedDB:', cachedDb);
async function connectToDatabase(uri) {
if (cachedDb) {
console.log('=> using cached database instance');
return cachedDb;
}
// If no connection is cached, create a new one
const db = await mongoose.connect(uri, { useNewUrlParser: true });
console.log('New MongoDB Connected');
// Cache the database connection and return the connection
cachedDb = db;
return db;
}
export default async () => {
await connectToDatabase(process.env.MONGODB_URI);
};`
Then call this custom mongoose function in any lamda that needs a mongoDB connection:
`import express from "express";
import mongoose from "../lib/mongoose";
const app = express();
// #route Get api/connect
// #desc Tests post route
// #access Public
app.get("*", async (req, res) => {
await mongoose();
// Code to query your DB or whatever here
});
export default app;`
You don't have to use express of course, but I personally haven't moved on to newer solutions. One of these days I'll learn micro.js.

Resources