Open lucianonooijen opened 5 years ago
For me this did the trick
afterAll(async () => { await new Promise(resolve => setTimeout(() => resolve(), 500)); // avoid jest open handle error });
Seems like it needs some more ticks to close that handle.
This nice. Solve the issue for me. Thanks.
My issue was actually being caused by
express-session
middleware. I suspect something abnormal was happening with supertest requests, quick workaround was to include this in any tests using supertestjest.mock('express-session', () => (options: SessionOptions) => (req: Request, res: Response, next: NextFunction) => { next(); });
Can you please elaborate on how you did this? I don't understand how the SessionOptions was passed into the function, it keeps throwing errors, a more detailed snippet would do, Thanks. I have a strong feeling that the problem is from express session. @cif
I literally just wasted 4 hours on this, had to use several of the solutions mentioned here. What FINALLY worked for me after literally trying every single one of them was a combination of @tksilicon solution + @lukaswilkeer + @carlafranca
To summarize I use the timeout in the afterAll, and I pass in --forceExit --detectOpenHandles --maxWorkers=1 and it finally worked.
I tried each of these on their own, but still had issues. The port mapping solution that so many had luck with did absolutely nothing for me either.
@rimiti this is by far the most requested feature (bugfix really), especially when considering the activity on these two issues which have the same root cause. https://github.com/visionmedia/supertest/issues/437 https://github.com/visionmedia/supertest/issues/436
If you are unable to provide the solution anytime in the near future, would you be able to give some guidance on where to start in the codebase so someone from the community is more likely to pick this up?
Or do you think this might be an issue with Jest and not supertest?
@cif The issue is not usually caused by
express-session
itself, but rather by the particular store used. I usually rely on the defaultMemoryStore
on tests, if it is really hard to properly shutdown the upstream connection to the store.
I'm using express-session and redis store. Can you share your solution if you have one?
Nothing in this thread has worked for me.
Weirdly, if I run the whole suite, Jest exists correctly.
I'm also seeing the same thing -- simple server test, TCPSERVERWRAP handle stays open:
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPSERVERWRAP
24 | test('should get an example by id', () =>
25 | request(Server)
> 26 | .get('/api/v1/examples/2')
| ^
27 | .expect('Content-Type', /json/)
28 | .then(r => {
29 | expect(r.body).toBeInstanceOf(Object)
at Test.Object.<anonymous>.Test.serverAddress (../../node_modules/supertest/lib/test.js:59:33)
at new Test (../../node_modules/supertest/lib/test.js:36:12)
at Object.obj.(anonymous function) [as get] (../../node_modules/supertest/index.js:25:14)
at Object.test (test/examples.controller.spec.ts:26:8)
I tried adding the afterAll
timeout suggested above, but that doesn't fix it. I'm using .then
rather than await
; should be the same, right? (@elucidsoft 's idea of --forceExit --detectOpenHandles
along with the afterAll
timeout does "fix" it, I don't see the error and it does exit, but I expect the root cause is still there.)
Running jest 24.9.0, supertest 4.0.2, express 4.17.1, Linux Ubuntu 18.04.
in my case I was opening a REDIS client, and indeed when I did not have rc.quit(), it was kept open. I added rc.quit() and jest ended gracefully.
Imo, it is not a Jest issue. There is a way to add some extra logic to close sockets at server close by maintaining a ref of available sockets for dev (the default behavior is good for production and graceful reload):
@AlexisNava
Try this in your app.test
const request = require('supertest'); const app = require('../app'); describe('App Request', () => { test('should responds with 404', async (done) => { const result = await request(app).get('/'); expect(result.status).toBe(404); done(); }); });
did the trick for me, but linter complains not to use callbacks. I know it's not SO and perhaps the question is more about Jest itself, but still, could someone add some clarity to that behaviour and why above solution is required?
afterAll(async () => {
await sleep(500)
})
works too.
I am facing this same issue. The strange part is that if I am using both flags --detectOpenHandles --forceExit
, if I run it locally, there are no warnings but when running it via CircleCI, it gives me a warning saying it detected a potentially open handle. However, using the above flags does result in the tests passing so it's a non-issue for now other than the annoying warning on my console.
@AlexisNava
Try this in your app.test
const request = require('supertest'); const app = require('../app'); describe('App Request', () => { test('should responds with 404', async (done) => { const result = await request(app).get('/'); expect(result.status).toBe(404); done(); }); });
but the test never exist :(
@AlexisNava
Try this in your app.test
const request = require('supertest'); const app = require('../app'); describe('App Request', () => { test('should responds with 404', async (done) => { const result = await request(app).get('/'); expect(result.status).toBe(404); done(); }); });
According what I know, await should not work work with done
got it working with just jest/supertest/mongoose (removing mockgoose). Thanks for the help.
let server; beforeAll(async (done) => { await mongoose.connect('mongodb://localhost:27017/testdb'); server = app.listen(4000, () => { global.agent = request.agent(server); done(); }); }); afterAll(async () => { await server.close(); await mongoose.disconnect(); });
Now time to figure out the issue with mockgoose joy
This solves my issue.
describe('AppController (e2e)', () => { let app: INestApplication;
beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => { return request(app.getHttpServer()) .get('/') .expect(200) .expect('Hello World!'); });
it('/weather (GET)', () => { return request(app.getHttpServer()) .get('/weather') .expect(200) .expect('weather'); })
afterEach( async () => { await app.close() }); });
The key for that is to add
afterEach( async () => {
await app.close()
});
how to do this with firestore based application
It can be simplified to
afterAll(async () => {
await new Promise(resolve => setTimeout(resolve, 500));
});
I found simple open handle keeps case and that solution.
Open handle keeps case test code
const request = require('supertest')
const app = (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('okay')
}
test('async await', async () => {
const res = await request(app).get('/')
expect(res.status).toBe(200)
})
Open handle keeps result
PASS test/failure.test.js
✓ async await (106ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.041s
Ran all test suites matching /failure/i.
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPSERVERWRAP
9 |
10 | test('async await', async () => {
> 11 | const res = await request(app).get('/')
| ^
12 | expect(res.status).toBe(200)
13 | })
14 |
at Test.serverAddress (node_modules/supertest/lib/test.js:59:33)
at new Test (node_modules/supertest/lib/test.js:36:12)
at Object.get (node_modules/supertest/index.js:25:14)
at Object.test (test/failure.test.js:11:34)
Done in 2.58s.
Success case test code
const request = require('supertest')
const app = (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('okay')
}
test('async await with done', async (done) => { // use callback
const res = await request(app).get('/')
expect(res.status).toBe(200)
done() // exec callback after expect
})
Success result
PASS test/success.test.js
✓ async await with done (111ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.869s
Ran all test suites matching /success/i.
Done in 2.14s.
I think this case is Jest probrem. Because it's not mentioned in the manual.
Passing done to the test and invoking it at the end resolved this for me, as mentioned by @soultoru above.
test("returns a title of a game when passed a valid product id", async (done) => {
await request(app).get("/description/title/1").expect(200);
done();
});
It worked for me just passing the callback done
at the end(done)
of the last test, like that:
it('title...', done => {
return request(app.getHttpServer())
...
.end(done);
});
Finally this worked for me, hope this helps to others. I've been banging my head for hours, even the simplest tests with supertests were always kept hanging.
const srv = app.listen();
const request = supertest(srv);
request.get('/get')
.expect(200)
.end(res => {
// expect(res.body).toBe('done');
done();
srv.close();
});
And by the way, how did I find it? by using https://www.npmjs.com/package/leaked-handles.
This is my working example, just in case it may help:
v12.13.1
4.0.2
26.1.0
import request from 'supertest';
import * as dotenv from 'dotenv';
// this is a private package which exports also a mongoose reference. The connection to the db is done elsewhere.
import { mongoose } from '@sostariffe/bootstrappers';
import bootstrap, { BootstrapResponse } from '../src/libs/bootstrap';
let bootType: BootstrapResponse;
beforeAll(async () => {
dotenv.config();
bootType = await bootstrap();
});
afterAll((done) => {
bootType.server.close((err) => {
if (err) {
console.error(err);
}
mongoose.connection.close((errMongo: Error) => {
if (errMongo) {
console.error(err);
}
done();
});
});
});
it('Verify route', (done) => {
request(bootType.app)
.post(`${process.env.APP_BASE_PATH}/verify`)
.set('Accept', 'application/json')
.send({ param: ['hey'] })
.expect(200)
.then((response) => {
expect(response.body).not.toBeNull();
done();
});
});
where bootstrap
function is defined as the following:
import express, { Request, Response, NextFunction } from 'express';
import { Server } from 'http';
import { bootstrapMongoose } from '@sostariffe/bootstrappers';
import blacklist from '../routes/blacklist';
import verify from '../routes/verify';
export type BootstrapResponse = {
server: Server,
app: express.Express,
}
/* This function boots a listening express instance and mongoose (hidden by a private function) */
export default async function bootstrap(): Promise<BootstrapResponse> {
const bootstrapExpress = new Promise<BootstrapResponse>((resolve, reject) => {
const app = express();
const port = process.env.PORT;
app.disable('x-powered-by');
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
app.use(`${process.env.APP_BASE_PATH}/blacklist`, blacklist);
app.use(`${process.env.APP_BASE_PATH}/verify`, verify);
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
const statusCode = res.statusCode || 500;
res.status(statusCode).json({
error: true,
description: err.message,
});
});
const server = app.listen(port, () => {
console.log(`Server listening at http://localhost:${port}`);
resolve({
app,
server,
});
});
});
const [bootstrapResponse] = await Promise.all([
bootstrapExpress,
bootstrapMongoose(),
]);
return bootstrapResponse;
}
Here is my solution.
index.js
import env from 'dotenv'
import express from 'express'
const app: any = express()
const PORT = process.env.PORT || 4000
import middlewares from '../../middleware/common'
import Logger, { logServerStatus } from '../../log/Logger'
env.config({ path: '.env' })
Logger()
middlewares.forEach((mdlware) => app.use(mdlware))
// HTTP SERVER
// SAVE THE EXPRESS CONNECTION IN A CONST
const server = app.listen(PORT, () => logServerStatus(PORT))
// EXPORT EXPRESS APP & SERVER CONNECTION
export default server
export {app}
test.js
import dbHandler from './dbConnection'
import server,{app} from './server/index'
import supertest from 'supertest'
const request = supertest(app) // PASS THE EXPRESS APP
beforeAll(async () => {
await dbHandler.connect()
})
afterAll(async (done) => {
await dbHandler.disconnect()
await server.close() // CLOSE THE SERVER CONNECTION
await new Promise(resolve => setTimeout(() => resolve(), 500)); // PLUS THE HACK PROVIDED BY @yss14
done()
})
Hello there, in my case I have the same problem and jest --detectLeaks --detectOpenHandles
shows nothing
this is the repo https://github.com/andrescabana86/epic-react-boilerplate
only one test, no services, nothing should be keeping Jest from the exit but still happening
I've tested with node v14.04 and v12.18.
On both works, but on node v12 shows a warning about Node Target Mapping (ts-jest config). On the other hand, you could try --forceExit.
Em sáb., 7 de nov. de 2020 às 16:30, Andres Cabana notifications@github.com escreveu:
Hello there, in my case I have the same problem and jest --detectLeaks --detectOpenHandles shows nothing
[image: Screen Shot 2020-11-07 at 16 28 51] https://user-images.githubusercontent.com/1840542/98449873-70c6b580-2116-11eb-9f9b-52e2a53a164b.png
this is the repo https://github.com/andrescabana86/epic-react-boilerplate
only one test, no services, nothing should be keeping Jest from the exit but still happening
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/visionmedia/supertest/issues/520#issuecomment-723485071, or unsubscribe https://github.com/notifications/unsubscribe-auth/AANZZXDWLENVKN2I5BNWXJLSOWN67ANCNFSM4GBUADRQ .
-- Lukas Wilkeer Desenvolvedor, empreendedor e aventureiro. +55 031 9413-8900
cafeepixel.com.br http://cafeepixel.com.brFaça parte do GDG. http://gdgbh.org
This worked well for me ...
import request from 'supertest'
import app from '../../server'
let server: Server
let agent: request.SuperAgentTest
beforeEach((done) => {
server = app.listen(4000, () => {
agent = request.agent(server)
done()
})
})
afterEach((done) => {
server.close(done)
})
However, I had to make the following changes to my server file:
if (process.env.NODE_ENV !== 'test') {
app.listen(appConfig.port, () => {
console.log(`Server running on port ${appConfig.port}`)
})
}
export default app
The above change means that the server still starts when you run npm start
but does not start the normal server when you are testing.
My npm start command from package.json is:
"scripts": {
"test": "cross-env NODE_ENV=test jest --detectOpenHandles",
}
I have found the reason and the solution for me. It was because I had readonly folder inside project, that was created by Docker volume. After removing this folder this error have gone away.
This is my case. I tested REST API endpoints as shown in example at Testing | NestJS.
The problem code is
it(`/GET cats`, () => {
// This causes the problem.
return request(app.getHttpServer())
.get('/cats')
.expect(200)
.expect({
data: catsService.findAll(),
});
});
And the solution is to use await
instead of return
:
it(`/GET cats`, () => {
await request(app.getHttpServer())
.get('/cats')
.expect(200)
.expect({
data: catsService.findAll(),
});
});
None of the above mentioned worked for me but somehow below code worked
jest.config.js
module.exports = {
....
globalTeardown: 'test-teardown-globals.js',
}
test-teardown-globals.js
module.exports = () => {
process.exit(0)
};
This article helped me find a clean solution. I got it working by manually configuring the agent as mentioned here https://github.com/visionmedia/supertest/issues/520#issuecomment-436071071
However I couldn't figure out how to make this setup and teardown reusable so after reading this article: https://rahmanfadhil.com/test-express-with-supertest/ I realised I can just export a helper function that creates a new app and use it like:
const request = require('supertest')
const { createServer } = require('./index')
const app = createServer()
it('can get a token given valid auth credentials', async () => {
const res = await request(app)
.post('/login')
.send({
username: 'tester',
password: 'password'
})
expect(res.statusCode).toEqual(200)
expect(res.body).toHaveProperty('token')
})
I was having an issue with this case because I was using Sequelize and a test database to run queries but the database keeps open after request.
My solution was:
const sequelizeDatabase = require("../../src/config/sequelizeDatabase");
afterAll(() => {
sequelizeDatabase.close();
});
@jonathansamines you mean passing the done like in your example?
let server; beforeEach((done) => { server = app.listen(4000, (err) => { if (err) return done(err); global.agent = request.agent(server); done(); }); }); afterEach((done) => { return server && server.close(done); });
this is not solving it either
done helps me :)
I added this condition around app.listen process.env.NODE_ENV !== 'test'. Works like charm
if (process.env.NODE_ENV !== 'test') {
app.listen(port, ....)
}
I solved this issue simply passing my app hosting URL to request as an argument like
const request = require("supertest");
describe("Index file Tests", () => {
it("should handle get request for /api/ping", async (done) => {
const response = await request("http://localhost:2021").get("/api/ping");
expect(response.status).toBe(200);
expect(response.body).toEqual({ success: true });
done();
});
});
Three years later and I still can't get this to work. The most basic setup isn't going for me: no databases, no extraneous middleware, no other resources.
const express = require('express')
const request = require('supertest')
test('should not leak memory', async () => {
const app = express()
await request(app).get('/')
})
❯ jest --detectOpenHandles --detectLeaks
FAIL src/memory-leak.test.js
● Test suite failed to run
EXPERIMENTAL FEATURE!
Your test suite is leaking memory. Please ensure all references are cleaned.
There is a number of things that can leak memory:
- Async operations that have not finished (e.g. fs.readFile).
- Timers not properly mocked (e.g. setInterval, setTimeout).
- Keeping references to the global scope.
I've tried any and all combinations of .end(done)
, async
usages and manual server
/agent
closing mentioned in this thread and nothing worked for me. Did anyone managed to get this running without leaking? Maybe even a false positive from jest
?
I'd chased this down, just to find out that it was a simple forgetfulness on my part. Our application fell back on the database that wasn't set up on CI or stubbed, causing a long wait that manifested in
TCPSERVERWRAP
.tl;dr Check your application for other reasons that your request could hang
Hi sbonami, could you please tell us more on how to check that "the db is set up on CI or stubbed"; I don't know what does that mean.
None of the above mentioned worked for me but somehow below code worked
jest.config.js
module.exports = { .... globalTeardown: 'test-teardown-globals.js', }
test-teardown-globals.js
module.exports = () => { process.exit(0) };
This was the solution for me.
Thanks @paulisloud, it did the trick 👍
module.exports = () => { process.exit(0) };
This solved the problem when I run jest --runInBand --detectOpenHandles
but when runing normally the warning is still there. Any suggestions?
For people who're testing Strapi
+ supertest
, I've realized that to prevent this log, I need to destroy
all connections on afterAll
:
// <rootDir>/tests/app.test.js
afterAll(async () => {
const dbSettings = strapi.config.get('database.connections.default.settings');
//This will delete test database after all tests
if (dbSettings && dbSettings.filename) {
const tmpDbFile = `${__dirname}/../${dbSettings.filename}`;
if (fs.existsSync(tmpDbFile)) {
fs.unlinkSync(tmpDbFile);
}
}
await strapi.connections.default.context.destroy(); // <- THIS DOES THE TRICK
})
give your app a recognisable name by setting process.title so if you're running a Koa server from a node script as part of you JEST setup you can
process.title="MyServer
you could start it from either cli, npm or jest-global-setup.js
then in jest-global-teardown.js you just invoke
killProcess("MyServer)
you could for instance use : https://www.npmjs.com/package/kill-process-by-name
see: https://jestjs.io/docs/configuration#globalteardown-string
jest - ^27.2.5, node - v16.11.1, npm = 8.0.0
Resolving the timeouts in the afterAll() section helped me as well. But here is something unusual that I have found = if there is a function declaration in the old format (i.e. function funcation_name() {}), then I used to get this error with TLSWRAP. How I solved it is by converting all such functions into arrow functions. I hope this helps someone if it is not already covered.
For me this did the trick
afterAll(async () => { await new Promise(resolve => setTimeout(() => resolve(), 500)); // avoid jest open handle error });
Seems like it needs some more ticks to close that handle.
@yss14 Thanks for your help, It solved the issue for me too, which I was chasing for couple of days but can you elaborate why this resolve the issues?
Hi! may I ask you where did you pass that piece of code? within describe()? Or outside of that? I saw many articles that say just place the afterAll() on top of your code but that doesn't do the trick for me. and actually placing it within the file neither =/
Howdy everyone. Figured I would chime in. I was running into this issue and it was driving me absolutely mad. I have discovered something that may be of use to folks here.
First, I discovered a connection between errors in Express itself and this behavior where it wouldn't shut down. My specific problem is a transitive dependency clash leading to a "called on undefined" error, but that specific detail is immaterial to this. My point is, Express itself was blowing up when trying to process a request.
I can prove this in my application because the same test I was having this "won't shut down" bug on, if I run the app and call it with postman, fails with that same "called on undefined" error. Most importantly, Express itself completely crashes when this happens.
Anyway, I added the following to my application:
process.on('uncaughtException', (err) => {
console.log('UncaughtException', err);
});
This is to catch and gracefully log any exceptions like this. As soon as I had this in place, all of my "express won't shut down" errors went away. I can run my test, it fails (due to the aforementioned "called on undefined" bug that I still haven't gotten around to fixing because I've been stuck on this express shutting down issue), but then the test suite gracefully shut down.
I am using this solution alone, without applying any of the other hack-y fixes in this thread. I'm hoping maybe this helps someone else who is struggling with this.
PS. One more thing: always use timeout()
on your supertest tests. I noticed one more thing, where the individual test may hang for a long time on a problem, and so long as I have that timeout in place it'll close gracefully.
@craigmiller160 Can you explain your comment on timeout() use? Jest has its own timeout, do these interrelate?
For me, it was solved with a condition for adding the listener to the app if not 'test'. Jest run as NODE_ENV=test
if (process.env.NODE_ENV !== 'test') {
app.listen(port, (): void => {
console.log(`Server running on port ${port}`);
});
}
And for avoiding this warning (https://github.com/lorenwest/node-config/wiki/Strict-Mode), I added a config json file with "env": "test",
.
Hi @niftylettuce , is there any workaround for this? None of the above solutions seem to work and this is coming in very simple individual tests as well. Can't use custom ports for 100+ tests as sometimes, they collide and the tests fail. Does anyone else have any solution? Even a slight help is appreciated. Much Thanks!
Error:
Env:
Testcode:
Full console output: