mongoose.connect leaves open handles after jest test - node.js

How to get rid of the openHandles left by mongoose.connect in jest test
const { startTestDB } = require("../conn");
const mongoose = require("mongoose");
const request = require("supertest");
const faker = require("faker");
const app = require("../app");
const fs = require("fs");
describe("test1: ", () => {
beforeAll(() => {
startTestDB()
.then(() => {
console.log("Connected to db.");
})
.catch((e) => {
console.log(e);
});
});
afterAll(() => {
mongoose
.disconnect()
.then(() => {
console.log('db connection is closed');
})
.catch((e) => {
console.error(e);
});
});
it("Should save a new user", async () =>
request(app)
.post("/save")
.send({
name: faker.internet.userName(),
email: faker.internet.email()
})
.expect(201));
});
This is an example code that has the same problem, when I run npx jest --detectOpenHandles I find one at mongoose.connect, as per my knowledge open handles occur because of async operations that didn't finish before tests, but I clearly close the connection in afterAll.
The Error Message I got:
● TLSWRAP
9 |
10 | module.exports.startTestDB = () =>
> 11 | mongoose.connect(
| ^
12 | `mongodb+srv://${mongodb.user}:${mongodb.password}#${mongodb.host}/${mongodb.db}-test?retryWrites=true&w=majority`,
13 | {
14 | useNewUrlParser: true,
at parseSrvConnectionString (node_modules/mongodb/lib/core/uri_parser.js:67:7)
at parseConnectionString (node_modules/mongodb/lib/core/uri_parser.js:597:12)
at connect (node_modules/mongodb/lib/operations/connect.js:282:3)
at node_modules/mongodb/lib/mongo_client.js:260:5
at maybePromise (node_modules/mongodb/lib/utils.js:692:3)
at MongoClient.Object.<anonymous>.MongoClient.connect (node_modules/mongodb/lib/mongo_client.js:256:10)
at node_modules/mongoose/lib/connection.js:835:12
at NativeConnection.Object.<anonymous>.Connection.openUri (node_modules/mongoose/lib/connection.js:832:19)
at node_modules/mongoose/lib/index.js:351:10
at node_modules/mongoose/lib/helpers/promiseOrCallback.js:32:5
at promiseOrCallback (node_modules/mongoose/lib/helpers/promiseOrCallback.js:31:10)
at Mongoose.Object.<anonymous>.Mongoose._promiseOrCallback (node_modules/mongoose/lib/index.js:1149:10)
at Mongoose.connect (node_modules/mongoose/lib/index.js:350:20)
at startTestDB (conn.js:11:12)
at tests/s.test.js:10:5
at TestScheduler.scheduleTests (node_modules/#jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/#jest/core/build/runJest.js:387:19)
at _run10000 (node_modules/#jest/core/build/cli/index.js:408:7)
at runCLI (node_modules/#jest/core/build/cli/index.js:261:3)

I am reposting my answer from this question jest and mongoose - jest has detected opened handles as this was my solution for the problem.
closing the connection as many others have proposed did not work for me.
after many hours of searching for a solution on SO and github issues, I came across this github thread https://github.com/visionmedia/supertest/issues/520 with multiple solutions offered. what was finally working for me was implementing a global-teardown-file in my root directory like so:
// test-teardown-globals.js
module.exports = () => {
process.exit(0);
};
and also by adjusting my jest.config.js slightly
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
globalTeardown: '<rootDir>/test-teardown-globals.js',
};
UPDATE: although this works this solution is only viable for developing and running tests locally. I also prefer Özgür Atmaca's solution by disconnecting before reconnecting in the beforeAll block

I am too reposting my answer here: https://stackoverflow.com/a/73673449/4012944
Mongoose web site clearly does not recommend using Jest with Mongoose apps and does not recommend timers or global setups/teardowns.
Such workaround feels more natural to me.
beforeAll(async () => {
await mongoose.disconnect();
await mongoose.connect(MONGODB_URL, MONGODB_OPTIONS);
});
Hope it helps!

Related

Best way to setup files for jest and mongodb

Hihi.
I'm having a problem where jest it's sometimes failing a couple of tests randomly, most of the time because of this error "mongodb memory server cannot perform operation: a background operation is currently running for collection".
In another post I read something about building differents mongo instance for each block of tests.
What I have so far is a globalsetup file where I start the mongo replica set like this:
// global.ts
import { MongoMemoryReplSet } from "mongodb-memory-server";
const replSet = new MongoMemoryReplSet({
replSet: { storageEngine: "wiredTiger" },
});
module.exports = async () => {
await replSet.waitUntilRunning();
const uri = await replSet.getUri();
process.env.MONGODB_URI = uri;
};
and my db.ts is like this
// db.ts
export const connect = async () => {
mongoose.set("useFindAndModify", false);
const conn = mongoose.connect(
process.env.MONGODB_URI || config.connectionString, connectionSettings
);
When trying to call it from a test file I do something like this
// test.spec.ts
import db from "./db";
beforeAll(async () => {
await db.connect();
});
afterAll(async (done) => {
await db.dropCollections();
await db.disconnect(done);
});
beforeEach(async () => {
await seed();
});
describe('Some test', () => {
it('Should not fail and get the seeders', () => {
// some random tests using the seeds values
})
})
What I read in that post is instead of using globalSetup use a setupFile that will run for every test instead of one globally and then I MIGHT be able to solve the concurrency issue I have with my tests.
So, to conclude, does anyone knows if there is a proper way to configure the mongodb in memory or if I am doing something THAT BAD that is allowing this to happend or if is there any kind of improvement I can do that will prevent "mongodb memory server cannot perform operation: a background operation is currently running for collection" this to happen?

How to close Express server inside Jest afterAll hook

I am trying to write integration tests for my Express server using Jest. Since Jest runs tests in parallel (and I would like to avoid running tests in sequence using --runInBand), I am using the get-port library to find a random available port so that different test suites don't have port collisions.
My tests all run successfully, the only problem is that the server is failing to close down properly inside the afterAll hook. This causes Jest to print the following in the console...
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.
When I use the --detectOpenHandles flag, Jest just hangs after tests complete. Nothing gets printed to the console.
Here is my test code...
let axios = require('axios')
const getPort = require('get-port')
const { app } = require('../../index')
const { Todo } = require('../../models')
// set reference to server to access
// from within afterAll hook
let server
beforeAll(async () => {
const port = await getPort()
axios = axios.create({ baseURL: `http://localhost:${port}` })
server = app.listen(port)
})
afterAll(() => {
server.close()
})
describe('GET /todos', () => {
it('returns a list of todos', async () => {
const { data: todos } = await axios.get('/todos')
todos.forEach(todo => {
expect(Todo.validate(todo)).toEqual(true)
})
})
})
I am on that github thread on this issue. Here is exactly the configuration that works for me. In package.json
"test": "jest --no-cache --detectOpenHandles --runInBand --forceExit",
Here is the configuration in test file
afterEach(async () => {
await server.close();
});
afterAll(async () => {
await new Promise(resolve => setTimeout(() => resolve(), 10000)); // avoid jest open handle error
});
beforeEach(() => {
// eslint-disable-next-line global-require
server = require('../index');
jest.setTimeout(30000);
});
OR you have only afterAll to set timeout and settimeout for each test in the test body individually.That's example below
afterEach(async () => {
await server.close();
});
afterAll(async () => {
await new Promise(resolve => setTimeout(() => resolve(), 10000)); // avoid jest open handle error
});
beforeEach(() => {
// eslint-disable-next-line global-require
server = require('../index');
});
describe('POST /customers', () => {
jest.setTimeout(30000);
test('It creates a customer', async () => {
const r = Math.random()
.toString(36)
.substring(7);
const response = await request(server)
.post('/customers')
.query({
name: r,
email: `${r}#${r}.com`,
password: 'beautiful',
});
// console.log(response.body);
expect(response.body).toHaveProperty('customer');
expect(response.body).toHaveProperty('accessToken');
expect(response.statusCode).toBe(200);
});
});
The root cause is that the express app server is still running after the tests complete. So the solution is to close the server.
In the main server file:
export const server = app.listen(...)
In the test file:
import { server } from './main-server-file'
afterAll(() => {
server.close();
});
Using nodejs 17.4.0, jest 27.5.1, supertest 6.2.2. Running test with
cross-env NODE_OPTIONS=--experimental-vm-modules NODE_ENV=test jest

Node.js: Jest + redis.quit() but open handle warning persists

I'm trying to run an integration test with redis and jest.
It always throws the "Jest has detected the following 1 open handle potentially keeping Jest from exiting:" error when running with --detectOpenHandles.
It doesn't hang so the socket is closing, but how do I write it so it doesn't throw that warning?
My code
import redis from 'redis';
let red: redis.RedisClient;
beforeAll(() => {
red = redis.createClient();
});
afterAll((done) => {
red.quit(() => {
done();
});
});
test('redis', (done) => {
red.set('a', 'b', (err, data) => {
console.log(err);
red.get('a', (err, data) => {
console.log(data);
done();
});
});
});
Warning
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPWRAP
3 |
4 | beforeAll(() => {
> 5 | red = redis.createClient();
| ^
6 | });
7 |
8 | afterAll((done) => {
at RedisClient.Object.<anonymous>.RedisClient.create_stream (node_modules/redis/index.js:251:31)
at new RedisClient (node_modules/redis/index.js:159:10)
at Object.<anonymous>.exports.createClient (node_modules/redis/index.js:1089:12)
at Object.<anonymous>.beforeAll (src/sockets/redis.integration.test.ts:5:17)
Running the compiled code with regular jest throws the same warning. The compiled code looks nearly identical.
Figured it out after posting. I forgot to post the answer here.
It has to do with how redis.quit() handles its callback.
It creates a new thread so we need to wait for the entire eventloop stack to cycle again. See below for the workaround.
async function shutdown() {
await new Promise((resolve) => {
redis.quit(() => {
resolve();
});
});
// redis.quit() creates a thread to close the connection.
// We wait until all threads have been run once to ensure the connection closes.
await new Promise(resolve => setImmediate(resolve));
}
I have the same issue. I haven't figured it out to fix this but I have a workaround.
You can add jest --forceExit.
For info about forceExit: https://jestjs.io/docs/en/cli.html#forceexit
Fixed for me doing
const redis = require("redis");
const { promisify } = require("util");
const createRedisHelper = () => {
const client = redis.createClient(`redis://${process.env.REDIS_URI}`);
client.on("error", function (error) {
console.error(error);
});
const setAsync = promisify(client.set).bind(client);
const setCallValidateData = async (key, data) => {
return setAsync(key, JSON.stringify(data));
};
return {
setCallValidateData,
cleanCallValidateData: promisify(client.flushdb).bind(client),
end: promisify(client.end).bind(client),
};
};
module.exports = createRedisHelper;
Versions:
redis 3.0.2
node 12.6.1
The problem is that you are creating the redis client but not connecting, so calling later redis.quit will not work, since you have no connection.
try:
beforeAll(async () => {
red = redis.createClient();
await red.connect();
});

How to properly close mongoose connection in jest test

I have the following error after running my tests with --detectOpenHandles parameter
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● PROMISE
18 |
19 | mongoose.Promise = global.Promise;
> 20 | mongoose.connect(config.database.link, config.database.options);
| ^
21 |
22 |
23 | app.use(cors());
But my test includes mongoose.disconnect()
afterAll(() => {
return new Promise(res => mongoose.disconnect(() => {
res();
}));
});
I tried to change the afterAll function to something like this:
afterAll(async () => {
await mongoose.disconnect();
await mongoose.connection.close();
});
Also I tried to call con.disconnect inside of afterAll()
app.con = mongoose.connect(config.database.link, config.database.options);
// inside of afterAll
app.con.disconnect()
But I am still getting the same error message as shown above
Id like to reference #ÖzgürAtmaca 's answer from this question: https://stackoverflow.com/a/73673422/11895459
I started using this answer instead of my own solution which was only a workaround and not fit for any environment except locally.
By disconnecting before reconnecting to the database the error is gone.
beforeAll(async () => {
await mongoose.disconnect();
await mongoose.connect(url, {});
});
afterAll(async () => {
await mongoose.disconnect();
});

Test Node.js API with Jest (and mockgoose)

Two questions here:
1) Is Jest a good options to test Node.js (express) APIs?
2) I'm trying to use Jest with Mockgoose, but I can't figure out how to establish the connection and run tests after. Here is my final attempt before coming on SO:
const Mongoose = require('mongoose').Mongoose
const mongoose = new Mongoose()
mongoose.Promise = require('bluebird')
const mockgoose = require('mockgoose')
const connectDB = (cb) => () => {
return mockgoose(mongoose).then(() => {
return mongoose.connect('mongodb://test/testingDB', err => {
if (err) {
console.log('err is', err)
return process.exit()
}
return cb(() => {
console.log('END') // this is logged
mongoose.connection.close()
})
})
})
}
describe('test api', connectDB((end) => {
test('adds 1 + 2 to equal 3', () => {
expect(1 + 2).toBe(3)
})
end()
}))
The error is Your test suite must contain at least one test. This error makes a bit of sense to me but I can't figure out how to solve it. Any suggestions?
Output:
Test suite failed to run
Your test suite must contain at least one test.
Very late answer, but I hope it'll help.
If you pay attention, your describe block has no test function inside it.
The test function is actually inside the callback passed to describe.. kind of, the stack is complicated due to arrow function callbacks.
this example code will generate the same problem..
describe('tests',function(){
function cb() {
setTimeout(function(){
it('this does not work',function(end){
end();
});
},500);
}
cb();
setTimeout(function(){
it('also does not work',function(end){
end();
});
},500);
});
since the connection to mongo is async, when jest first scans the function to find "tests" inside the describe, it fails as there is none.
it may not look like it, but that's exactly what you're doing.
I think in this case your solution was a bit too clever(to the point it doesn't work), and breaking it down to simpler statements could've helped pinpointing this issue
var mongoose = require('mongoose');
// mongoose.connect('mongodb://localhost/animal', { useNewUrlParser: true });
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
var kittySchema = new mongoose.Schema({
name: String
});
var god = mongoose.model('god', kittySchema);
module.exports = god;
code for god.js file
describe("post api", () => {
//life cycle hooks for unit testing
beforeAll(() => {
mongoose.connect(
"mongodb://localhost/animal",
{ useNewUrlParser: true }
);
});
//test the api functions
test("post the data", async () => {
console.log("inside the test data ");
var silence = await new god({ name: "bigboss" }).save();
});
// disconnecting the mongoose connections
afterAll(() => {
// mongoose.connection.db.dropDatabase(function (err) {
// console.log('db dropped');
// // process.exit(0);
// });
mongoose.disconnect(done);
});
});
Jest testing code ..using jest we can we store the name in db

Resources