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();
});
Related
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!
Context
I have spiked a TCP Echo server and am trying to write integration tests for it. I'm familiar with testing but not asynchronously.
Desired Behaviour
I would like my tests to spy on logs to verify that the code is being executed. Any asynchronous code should be handled properly, but this is where my understanding falls through.
Problem
I am getting asynchronous errors:
Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "Server Ready".
Attempted to log "Client Connected".
And finally a warning:
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.
Code
import * as net from 'net';
export const runServer = async () => {
console.log('Initialising...');
const port: number = 4567;
const server = net.createServer((socket: net.Socket) => {
socket.write('Ready for input:\n');
console.log('Client Connected');
socket.on('data', (data) => {
echo(data, socket);
server.close();
})
socket.on('end', () => {
console.log('Client Disconnected');
});
});
server.listen(port, () => {
console.log('Server Ready');
});
server.on('error', (err) => {
console.log(err);
});
function echo(data: Buffer, socket: net.Socket) {
console.log('Input received')
socket.write(data)
};
return server;
}
Test
More such tests will be added when these are working as intended.
import * as index from '../../src/index';
import * as process from 'child_process';
test('the server accepts a connection', async () => {
const consoleSpy = spyOn(console, 'log');
try {
const server = await index.runServer();
await consoleConnect();
await consoleEcho();
await consoleStop();
} catch (error) {
console.log(error);
}
expect(consoleSpy).toHaveBeenCalledWith('Initialising...');
expect(consoleSpy).toHaveBeenCalledWith('Client Connected');
expect(consoleSpy).toHaveBeenCalledTimes(2);
})
const consoleConnect = async () => {
process.exec("netcat localhost 4567");
}
const consoleEcho = async () => {
process.exec("Echo!");
}
const consoleStop = async () => {
process.exec("\^C");
}
My overall question is how do I manage the events in such a way that the tests are able to run without async-related errors?
You are not properly waiting for your child processes to finish. Calls to exec return a ChildProcess object as documented here. They execute asynchronously so you need to wait for them to finish using the event emitter api.
Ex from docs
ls.on('exit', (code) => {
console.log(`child process exited with code ${code}`);
});
To use async await you need to convert to using a promise. Something like
return new Promise((resolve, reject) => {
ls.on('exit', (code) => {
resolve(code);
});
// Handle errors or ignore them. Whatevs.
}
You are closing your server on the first data event. You probably don't want to do that. At least wait until the end event so you have read all the data.
socket.on('data', (data) => {
echo(data, socket);
server.close(); // Remove this line
})
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();
});
I would like to add event listeners to a MongoDB connection to run something when the connection drops, each reconnection attempt and at a successful reconnection attempt.
I read all the official docs and the API, but I can't find a solution.
Currently, I have this, but only the timeout event works.
// If we didn't already initialize a 'MongoClient', initialize one and save it.
if(!this.client) this.client = new MongoClient();
this.connection = await this.client.connect(connectionString, this.settings);
this.client.server.on('connect', event => {
console.log(event);
});
this.client.server.on('error', event => {
console.log(event);
});
this.client.server.on('reconnect', event => {
console.log(event);
});
this.client.server.on('connections', event => {
console.log(event);
});
this.client.server.on('timeout', event => {
console.log(event);
});
this.client.server.on('all', event => {
console.log(event);
});
I tried the events listed here, and they work, but there is no "reconnect" event:
http://mongodb.github.io/node-mongodb-native/2.2/reference/management/sdam-monitoring/
Sure you can. Basically though you need to tap into the EventEmitter at a lower level than basically off the MongoClient itself.
You can clearly see that such things exist since they are visible in "logging", which can be turned on in the driver via the setting:
{ "loggerLevel": "info" }
From then it's really just a matter of tapping into the actual source emitter. I've done these in the following listing, as well as including a little trick for getting the enumerated events from a given emitted, which was admittedly used by myself in tracking this down:
const MongoClient = require('mongodb').MongoClient;
function patchEmitter(emitter) {
var oldEmit = emitter.emit;
emitter.emit = function() {
var emitArgs = arguments;
console.log(emitArgs);
oldEmit.apply(emitter, arguments);
}
}
(async function() {
let db;
try {
const client = new MongoClient();
client.on('serverOpening', () => console.log('connected') );
db = await client.connect('mongodb://localhost/test', {
//loggerLevel: 'info'
});
//patchEmitter(db.s.topology);
db.s.topology.on('close', () => console.log('Connection closed') );
db.s.topology.on('reconnect', () => console.log('Reconnected') );
} catch(e) {
console.error(e)
}
})()
So those two listeners defined:
db.s.topology.on('close', () => console.log('Connection closed') );
db.s.topology.on('reconnect', () => console.log('Reconnected') );
Are going to fire when the connection drops, and when an reconnect is achieved. There are also other things like reconnect attempts which are also in the event emitter just like you would see with the loggerLevel setting turned on.
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