Starting an http server results in an open handle despite closing the server down after tests ran.
Steps to reproduce the behavior:
Create the following file named demo.test.js
:
const http = require('http');
describe('demo', () => {
let server;
beforeAll(() => {
server = http.createServer((req, res) => {
res.write('ok');
res.end();
});
server.listen();
});
afterAll(async() => {
await server.close();
});
test('my test', async () => {
});
});
Running with jest produces the following output
$ npx jest --detectOpenHandles .
PASS ./demo.test.js
demo
โ my test (2ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.759s
Ran all test suites matching /./i.
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
โ TCPSERVERWRAP
10 | res.end();
11 | });
> 12 | server.listen();
| ^
13 | });
14 |
15 | afterAll(() => {
at Object.listen (demo.test.js:12:16)
No open handles should be left.
npx envinfo --preset jest
Paste the results here:
$ npx envinfo --preset jest
npx: installed 1 in 1.938s
System:
OS: Linux 4.15 Ubuntu 18.04.1 LTS (Bionic Beaver)
CPU: x64 Intel(R) Core(TM) i7-4510U CPU @ 2.00GHz
Binaries:
Node: 10.8.0 - ~/.nvm/versions/node/v10.8.0/bin/node
npm: 6.2.0 - ~/.nvm/versions/node/v10.8.0/bin/npm
npmPackages:
jest: ^23.5.0 => 23.5.0
That's because you don't wait for it to close down. The http API is callback based, not promise.
This successfully closes down without a warning from detectOpenHandles
:
const http = require('http');
describe('demo', () => {
let server;
beforeAll(done => {
server = http.createServer((req, res) => {
res.write('ok');
res.end();
});
server.listen(done);
});
afterAll(done => {
server.close(done);
});
test('my test', async () => {});
});
Many thanks. That's interesting. I had tried the callback style for the server.close()
and it didn't make a difference. It seems the callback for server.listen()
is what does cause the TCPSERVERWRAP
.
Just to follow this up, should anybody read this because they're testing with supertest and express. The following leaves no open handles.
import supertest from 'supertest';
import express from 'express';
import http from 'http';
describe('demo test', () => {
let appp, server;
beforeAll(done => {
app = new express();
server = http.createServer(app);
server.listen(done);
});
afterAll(done => {
server.close(done);
});
it('returns 500', async () => {
const response = await supertest(app).get('/');
expect(response.status).toBe(500);
});
});
@pascalopitz I used the above, Jest still detects open handles
```
import supertest from 'supertest';
import http from 'http';
import app from '../app';
describe('demo test', () => {
let server;
beforeAll((done) => {
server = http.createServer(app);
server.listen(done);
});
afterAll((done) => {
server.close(done);
});
it('returns 200', async () => {
const response = await supertest(app).get('/');
expect(response.status).toBe(200);
});
});
````
After a lot of research, I stumbled on this Stackoverflow answer. I recreated my app.js without a server. Then I created an index.js that contains my server instance running. I imported the app.js into my test file and created a test server that I can close easily. Now, it works well.
app.js
````
import express from 'express';
const app = express();
app.get('*', (req, res) => res.status(200).json({ message: 'Project started' }));
export default app;
````
index.js
````
import app from './app';
const port = parseInt(process.env.PORT, 10) || 7000;
app.listen(port, () => console.log(Live at ${port}
));
````
app.spec.js
````
import supertest from 'supertest';
import http from 'http';
import app from '../app';
describe('demo test', () => {
let server;
let request;
beforeAll((done) => {
server = http.createServer(app);
server.listen(done);
request = supertest(server);
});
afterAll((done) => {
server.close(done);
});
it('returns 200', async () => {
const response = await request.get('/');
expect(response.status).toBe(200);
});
it('returns "Project started"', async () => {
const response = await request.get('/');
expect(response.body.message).toBe('Project started');
});
});
````
@jamesenejo this was my exact use-case as well. For my case, I kept app.js and index.js the same file, and just used a conditional so the server won't listen during testing:
if (NODE_ENV !== 'test') {
app.listen(PORT, HOST)
console.log(`Server running at http://${HOST}:${PORT}`)
}
The real key here (for me) is the beforeAll()
block that actually creates the server directly there. I was already closing my down in my afterAll()
but that just wasn't good enough. Thank you very much for the solution! ๐
For anyone still looking for solution. You need the following
afterAll(() => {
mongoose.disconnect();
});
mongoose remains active after the test cases, thats why Jest does not exit.
@gauravsoni1 Thank you for the hint! It works like charm.
Most helpful comment
Just to follow this up, should anybody read this because they're testing with supertest and express. The following leaves no open handles.