supertest icon indicating copy to clipboard operation
supertest copied to clipboard

Open handle keeps Jest from exiting ( TCPSERVERWRAP)

Open lucianonooijen opened this issue 6 years ago โ€ข 120 comments

Error:

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

โ— TCPSERVERWRAP

Env:

  • Node 10
  • Supertest 3.3.0
  • Jest 23.6

Testcode:

const request = require('supertest');
const app = require('../app');

describe('Test the status paths', () => {
    test('The GET / route should give status code 200', async () => {
        expect.assertions(1);
        const response = await request(app).get('/');
        expect(response.statusCode).toBe(200);
    });

    test('The GET /status route should give status code 200', async () => {
        expect.assertions(1);
        const response = await request(app).get('/status');
        expect(response.statusCode).toBe(200);
    });
});

Full console output:

 PASS  tests/app.test.js
  Test if test database is configured correctly
    โœ“ Jest should create a test database (54ms)
  Test the status paths
    โœ“ The GET / route should give status code 200 (28ms)
    โœ“ The GET /status route should give status code 200 (7ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        1.179s
Ran all test suites.

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  โ—  TCPSERVERWRAP

      27 |     test('The GET /status route should give status code 200', async () => {
      28 |         expect.assertions(1);
    > 29 |         const response = await request(app).get('/status');
         |                                             ^
      30 |         expect(response.statusCode).toBe(200);
      31 |     });
      32 | });

      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.get (tests/app.test.js:29:45)

^C

lucianonooijen avatar Nov 03 '18 21:11 lucianonooijen

@lucianonooijen Does it works if you manually invoke app.close after each test? (either by individually calling it, or through a afterEach block)

jonathansamines avatar Nov 04 '18 00:11 jonathansamines

@jonathansamines I tried adding

afterEach(() => app.close());

and

afterEach(app.close());

but both give the same error:

est the status paths โ€บ The GET /status route should give status code 200

    TypeError: app.close is not a function

       5 | const app = require('../app');
       6 |
    >  7 | afterEach(() => app.close());
         |                     ^
       8 |
       9 | describe('Test the status paths', () => {
      10 |     test('The GET / route should give status code 200', async() => {

      at Object.close (tests/supertest.test.js:7:21)
 FAIL  tests/supertest.test.js
  โ— Test suite failed to run
    TypeError: app.close is not a function
       5 | const app = require('../app');
       6 |
    >  7 | afterEach(app.close());
         |               ^
       8 |
       9 | describe('Test the status paths', () => {
      10 |     test('The GET / route should give status code 200', async() => {

      at Object.close (tests/supertest.test.js:7:15)

Did I invoke them wrong or am I missing something somewhere?

lucianonooijen avatar Nov 04 '18 13:11 lucianonooijen

+1 i could use help with this issue also, same env as above and same errors.

danielantelo avatar Nov 05 '18 15:11 danielantelo

@lucianonooijen @danielantelo Sorry about that, I sometimes confuse the express app with the server object.

When supertest is used in an express application not yet bound to a port, it will try to automatically bound it to a random port, holding a reference to the net server internally. To gain control over that process, you may want to try manually listen on a port yourself, so that you can close the connections manually.


let server, agent;

beforeEach((done) => {
    server = app.listen(4000, (err) => {
      if (err) return done(err);

       agent = request.agent(server); // since the application is already listening, it should use the allocated port
       done();
    });
});

afterEach((done) => {
  return  server && server.close(done);
});

it('description', async () => {
   // use agent instead of manually calling `request(app)` each time
   await agent.get('/some-path-relative-to-the-express-app')
});

As an alternative to setting up an agent, you can also use the end method to force supertest to close the automatic bound connection.

   // on each test
  it('the description', (done) => {
        request(app)
          .get('/some-path')
          .end(done);
  });

However, I am not sure how well this method plays with the promise based interface, because as you can see is expecting a callback.

References: https://github.com/visionmedia/supertest/blob/master/lib/test.js#L54

Hope it helps!

jonathansamines avatar Nov 05 '18 23:11 jonathansamines

thanks for the reply @jonathansamines , appreciate it! Still having issues tho.

This is my setupTestFrameworkScriptFile:

const request = require('supertest');
const Mockgoose = require('mockgoose').Mockgoose;
const app = require('./src/app');
const db = require('./db');
const fixtures = require('./tests/fixtures');

let server;

beforeEach(async () => {
  await db.connect(Mockgoose);
  await fixtures();
  server = await app.listen(4000);
  global.agent = request.agent(server);
});

afterEach(async () => {
  await server.close();
  await db.disconnect();
});

and then a test in another file:

describe('read', () => {
  test('lists all', async () => {
    const response = await global.agent.get('/get/all');
    expect(response.statusCode).toBe(200);
    expect(Array.isArray(response.body)).toBe(true);
    expect(response.body.length).toBe(3);
    expect(response.body).toMatchSnapshot();
  });
});

my tests pass, but jest does not exit due to open handles in global.agent.get('/get/all')

using .end() on the request does not work when using promises to assert values, i got the error superagent request was sent twice, because both .end() and .then() were called. Never call .end() if you use promises.

Any ideas what i am missing on the setup before/afters?

Much appreciated!

danielantelo avatar Nov 06 '18 09:11 danielantelo

@danielantelo @lucianonooijen Unless you are promisifying the server methods close and listen, that is unlikely to work, because they are expecting a callback and do not return a promise by default. You should either promisify those methods or use a callback based api

jonathansamines avatar Nov 06 '18 14:11 jonathansamines

@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

danielantelo avatar Nov 06 '18 15:11 danielantelo

@danielantelo What error are you getting while using the callback based api?

jonathansamines avatar Nov 06 '18 15:11 jonathansamines

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 ๐Ÿ˜‚

danielantelo avatar Nov 07 '18 10:11 danielantelo

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 ๐Ÿ˜‚

This fix doesn't work if you have multiple test files. The server doesn't seem to be closing properly and you will get port collisions when trying to start the server in the different test files.

yarn run v1.10.1
$ jest --forceExit
 PASS  __tests__/routes/reviews.test.js
 FAIL  __tests__/routes/movies.test.js (6.343s)
  โ— Console

    console.error node_modules/jest-jasmine2/build/jasmine/Env.js:157
      Unhandled error
    console.error node_modules/jest-jasmine2/build/jasmine/Env.js:158
      Error: listen EADDRINUSE :::5025
          at Object._errnoException (util.js:992:11)
          at _exceptionWithHostPort (util.js:1014:20)
          at Server.setupListenHandle [as _listen2] (net.js:1355:14)
          at listenInCluster (net.js:1396:12)
          at Server.listen (net.js:1480:7)

skykanin avatar Nov 08 '18 11:11 skykanin

@skykanin you are right, after i got mockgoose working i realised that when i ran multiple files there was still an issue.

With mockgoose i had to close the connection after each test, otherwise would leave open handles on a single file. But doing that on the server just breaks things further.

So think we are now stuck on the same issue.

const request = require('supertest');
const mongoose = require('mongoose');
const Mockgoose = require('mockgoose').Mockgoose;
const app = require('./src/app');
const fixtures = require('./tests/fixtures');

const mockgoose = new Mockgoose(mongoose);
let server;

afterEach(async () => {
  await mockgoose.helper.reset();
  await fixtures();
});

beforeAll(async done => {
  await mockgoose.prepareStorage();
  await mongoose.connect('mongodb://testdb');
  await fixtures();
  server = app.listen(4000, () => {
    global.agent = request.agent(server);
    done();
  });
});

afterAll(async () => {
  if (server) {
    await server.close();
  }
  mongoose.connections.forEach(async con => {
    await con.close();
  });
  await mockgoose.mongodHelper.mongoBin.childProcess.kill();
  await mockgoose.helper.reset();
  await mongoose.disconnect();
});

Jest23 runs the first file, then fails to run any other files and gives lots of async timeout errors.

danielantelo avatar Nov 08 '18 14:11 danielantelo

@danielantelo Sorry, I am not familiar with the mockgoose library. Most I can do, is to ask a couple of questions:

Is mongoose.connections.forEach method an special kind of object which properly deals with async/await? If not, I would rather do something like:

    await Promise.all(mongoose.connections.map(con => con.close()))

Here I am assumming con.close is a promise returning function, otherwise, you would probably have to wrap it with a promise.

Also, is server.close a promise aware function? (it is natively not) If not, you will have to wrap it so that it properly awaits for the connection to close.

jonathansamines avatar Nov 12 '18 01:11 jonathansamines

Are you using Apollo Engine by any chance? I was able to solve this problem by disabling the engine in Apollo Server when testing.

kramerc avatar Nov 14 '18 07:11 kramerc

I'm not using Apollo Engine and I still get the same error. end() helps, but it forces me to use the uglier async handling style. Interestingly, the open handle is detected only for one post() request, out of many other requests I have in tests.

jalooc avatar Dec 05 '18 18:12 jalooc

I have the same issue:

Node version: 11.0.0 npm version: 6.5.0

package.json


{
  "name": "authentication",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "lint": "eslint --fix server.js src/**",
    "start": "node server.js",
    "start:dev": "nodemon server.js",
    "test": "jest --forceExit --detectOpenHandles"
  },
  "husky": {
    "hooks": {
      "post-pull": "npm install",
      "pre-commit": "npm run lint && npm test"
    }
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^3.0.3",
    "body-parser": "^1.18.3",
    "dotenv": "^6.2.0",
    "express": "^4.16.4",
    "jsonwebtoken": "^8.4.0",
    "lodash": "^4.17.11",
    "morgan": "^1.9.1"
  },
  "devDependencies": {
    "eslint": "^5.11.1",
    "eslint-plugin-node": "^8.0.1",
    "eslint-plugin-security": "^1.4.0",
    "husky": "^1.3.1",
    "jest": "^23.6.0",
    "nodemon": "^1.18.9",
    "prettier": "^1.15.3",
    "supertest": "^3.3.0"
  }
}


test task in package.json


  "scripts": {
    "test": "jest --forceExit --detectOpenHandles"
  },

app.test.js


const request = require('supertest');
const app = require('../app');

describe('App Request', () => {
  it('should responds with 404', () => {
    request(app)
      .get('/')
      .expect('Content-Type', /json/)
      .expect(404)
      .end((error, response) => {
        if (error) {
          return error;
        }

        return response;
      });
  });
});

Error:


> jest --forceExit --detectOpenHandles

 PASS  src/test/app.test.js
  App Request
    โœ“ should responds with 404 (19ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.891s
Ran all test suites.

Jest has detected the following 1 open handle potentially keeping Jest from exiting:

  โ—  TCPSERVERWRAP

       5 |   it('should responds with 404', () => {
       6 |     request(app)
    >  7 |       .get('/')
         |        ^
       8 |       .expect('Content-Type', /json/)
       9 |       .expect(404)
      10 |       .end((error, response) => {

      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.get (src/test/app.test.js:7:8)

ae-lexs avatar Jan 10 '19 03:01 ae-lexs

@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();
  });
});

alfonsoal1983 avatar Jan 22 '19 10:01 alfonsoal1983

I am experiencing the same error. I suppose a solution for the time being is to use --forceExit with Jest.

marcospgp avatar Jan 22 '19 16:01 marcospgp

I found out that if you are using process.env in any test it also won't exit.

danieldaeschle avatar Feb 07 '19 21:02 danieldaeschle

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

sbonami avatar Feb 21 '19 18:02 sbonami

i also have the same problem, need help, did some solved it ?

buuug7 avatar Feb 26 '19 07:02 buuug7

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 avatar Mar 03 '19 17:03 yss14

@yss14 much thanks, it really works for me. anyone have this issue can try @yss14 's code

buuug7 avatar Mar 07 '19 03:03 buuug7

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?

sharmaar12 avatar Mar 23 '19 09:03 sharmaar12

i"m also interested in knowing how it solves the issue.

angristan avatar Mar 30 '19 17:03 angristan

@sharmaar12 @angristan I don't know the reason for sure, because I'm not familiar with the supertest code base. I guess that during the shutdown of supertest, somewhere the thread is not waiting for an async system operation to finish. Thus, the operation is delegated to the system but the execution is not blocked or does not wait for the async operation to finish. In the meantime, your test case execution finishes, jest is shuting down, and complains about an open handle.

If I'm wrong, please someone corrects me :)

yss14 avatar Mar 30 '19 18:03 yss14

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 didn't helped me. But I manged to force Jest to exit with this flag: --forceExit

However I am still getting the warring that Jest detected an open handle.

milenkovic avatar Apr 09 '19 10:04 milenkovic

Were any other solutions found for this issue? I have a similar problem. When I run my test suite normally, I get the warning that Jest did not exit one second after completion of the test run.

Calling with --detectLeaks finds a leak, and calling with --detectOpenHandles shows the same issue - TCPSERVERWRAP.

If I add @yss14 's code in the afterAll hook, something peculiar happens - running Jest normally still shows the open handle issue. Running with the --detectLeaks flag still shows a leak. Running with --detectOpenHandles shows nothing.

I'm testing a middleware function, and my suite looks like this:

// Hooks - Before All
beforeAll(async () => {
    await configureDatabase();
    app.use(auth).get('/', (req, res) => res.send());
});

// Hooks - After All
afterAll(async () => {
    await new Promise(resolve => setTimeout(() => resolve(), 500)); // avoid jest open handle error  
});

describe('Express Auth Middleware', () => {
    test('Should return 401 with an invalid token', async () => {
        await request(app)
            .get('/')
            .set('Authorization', 'Bearer 123')
            .send()
            .expect(401);
    });

    test('Should return 401 without an Authorization Header', async () => {
        await request(app)
            .get('/')
            .send()
            .expect(401);  
    });
    
    test('Should return 200 with a valid token', async () => {
        await request(app)
            .get('/')
            .set('Authorization', `Bearer ${userOneToken}`)
            .send()
            .expect(200);
    });
});

Binding to a port and closing manully does not work, nor does getting rid of async/await, and instead passing done into end (i.e passing not calling, so .end(done);.

Does anyone have any ideas? This is a serious issue because it seems that every test suite that relies on Express/Supertest is leaking memory, eventually to the point that my tests terminate with a JavaScript heap out of memory error.

Thank you.

jcorkhill avatar May 07 '19 16:05 jcorkhill

More detailed information for my above comment can be found in this SO question: https://stackoverflow.com/questions/56027190/jest-memory-leak-testing-express-middleware

jcorkhill avatar May 07 '19 16:05 jcorkhill

@lucianonooijen @danielantelo @marcospgp Did you ever find a solution other than --forceExit and the solution provided above? Thanks.

jcorkhill avatar May 08 '19 23:05 jcorkhill

@JamieCorkhill have you tried any of the suggestions stated at this comment?

If any of those worked for you, it is very likely that the actual issue is somewhere else in your application. I could help with that, but I'll need an isolated reproduction example to take a look at.

jonathansamines avatar May 09 '19 03:05 jonathansamines

@skykanin you are right, after i got mockgoose working i realised that when i ran multiple files there was still an issue.

With mockgoose i had to close the connection after each test, otherwise would leave open handles on a single file. But doing that on the server just breaks things further.

So think we are now stuck on the same issue.

const request = require('supertest');
const mongoose = require('mongoose');
const Mockgoose = require('mockgoose').Mockgoose;
const app = require('./src/app');
const fixtures = require('./tests/fixtures');

const mockgoose = new Mockgoose(mongoose);
let server;

afterEach(async () => {
  await mockgoose.helper.reset();
  await fixtures();
});

beforeAll(async done => {
  await mockgoose.prepareStorage();
  await mongoose.connect('mongodb://testdb');
  await fixtures();
  server = app.listen(4000, () => {
    global.agent = request.agent(server);
    done();
  });
});

afterAll(async () => {
  if (server) {
    await server.close();
  }
  mongoose.connections.forEach(async con => {
    await con.close();
  });
  await mockgoose.mongodHelper.mongoBin.childProcess.kill();
  await mockgoose.helper.reset();
  await mongoose.disconnect();
});

Jest23 runs the first file, then fails to run any other files and gives lots of async timeout errors.

Sorry for the super late response, but you can apply the same trick as supertest does to bound your server to a random port.

server.listen(0);

Of course, you can still get port collisions, but they are very unlikely.

jonathansamines avatar May 09 '19 03:05 jonathansamines

@jonathansamines I did try solution proposed in the comment you linked to, but I recieve the same response:

node_modules/env-cmd/bin/env-cmd.js config/test.env node_modules/jest/bin/jest.js auth.test.js
 PASS  __tests__/middleware/auth.test.js
  Express Auth Middleware
    โˆš Should return 401 with an invalid token (27ms)
    โˆš Should return 401 without an Authorization Header (4ms)
    โˆš Should return 200 with a valid token (9ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        3.246s, estimated 5s
Ran all test suites matching /auth.test.js/i.
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.

Running with --detectOpenHandles does not find anything.

The auth.test.js file contains only three tests:

const request = require('supertest');
const app = require('./../../src/app');

// Database Fixtures
const { userOneToken, configureDatabase } = require('./../fixtures/db');

// Testing
const auth = require('./../../src/middleware/auth');

let server, agent;

// Hooks - Before All
beforeAll(async (done) => {
    await configureDatabase();

    server = app.listen(4000, err => {
        if (err) return done(err);

        agent = request.agent(server);
        app.use(auth).get('/', (req, res) => res.send());

        done();
    });
});

// Hooks - After All
afterAll(done => {
    return server && server.close(done); 
});

describe('Express Auth Middleware', () => {
    test('Should return 401 with an invalid token', async () => {
        await agent
            .get('/')
            .set('Authorization', 'Bearer 123')
            .send()
            .expect(401);
    });

    test('Should return 401 without an Authorization Header', async () => {
        await agent
            .get('/')
            .send()
            .expect(401);  
    });
    
    test('Should return 200 with a valid token', async () => {
        await agent
            .get('/')
            .set('Authorization', `Bearer ${userOneToken}`)
            .send()
            .expect(200);
    });
});

I can try to create an isolated repro example, but you'll need a local MongoDB Server running.

Thank you.

jcorkhill avatar May 09 '19 03:05 jcorkhill

@JamieCorkhill that works for me

jonathansamines avatar May 09 '19 03:05 jonathansamines

@jonathansamines Great. Thanks for your time. I'll start building it now, and you'll be able to run the server as an npm script with environment variables pre-loaded.

jcorkhill avatar May 09 '19 03:05 jcorkhill

@jonathansamines Alright. Repro complete. Please bear with me if this does not suit your needs. It is my first time creating an isolated reproduction example.

The only script you can run is npm test, which will initialize the application with the required environment variables. That also means that firebase-admin will work because it's functioning of the mock and not expecting an API key.

A local MongoDB server will have to be running. I don't know if you are on Windows, but for ease, I have provided a .bat file. You just have to edit that file to include the path to mongod.exe and the --dbpath flag. That can be run with npm dev-db-server.

Here is the repository:

https://github.com/JamieCorkhill/Jest-Memory-Leak-Repro

Please let me know if there is anything else you need. I appreciate your time.

jcorkhill avatar May 09 '19 04:05 jcorkhill

Hi @JamieCorkhill I finally got some time to take a look. After some debugging, I noticed there was an open mongodb connection, which was never closed.

See a couple of changes I made to your example: https://github.com/JamieCorkhill/Jest-Memory-Leak-Repro/compare/master...jonathansamines:master

But, in summary:

  • I made the mongodb connection lazy, so that the tests could take control over the connection/disconnection process
  • Applied my suggestions to use an agent, instead of request directly.
  • Updated existing references to the connection, to invoke the lazy method.

jonathansamines avatar May 11 '19 03:05 jonathansamines

@jonathansamines Sorry for the late response. I did merge your changes and take a brief look, but I've been busy lately and have not yet had a chance to test if that fixes the memory leaks.

I'll get back to you later today.

Thank you for your help, Jamie

jcorkhill avatar May 11 '19 17:05 jcorkhill

@jonathansamines I just tested your changes and it worked fantastically without memory leaks.

I did have one question, though.

In auth.test.js, in the beforeAll hook, you have this:

// Hooks - Before All
beforeAll(async () => {
    connection = await connectionFactory();
    await configureDatabase();

    app.use(auth).get('/', (req, res) => res.send());

    new Promise((resolve, reject) => {
        server = app.listen(4000, (err) => {
            if (err) return reject(err);
            resolve();
        });
    });

    agent = request.agent(server);
});

Do you not have to await the promise to settle, as seen below, or do you just promisify the call to server.listen() so it works with the async callback to beforeAll?

// Hooks - Before All
beforeAll(async () => {
    connection = await connectionFactory();
    await configureDatabase();

    app.use(auth).get('/', (req, res) => res.send());

    await new Promise((resolve, reject) => {
        server = app.listen(4000, (err) => {
            if (err) return reject(err);
            resolve();
        });
    });

    agent = request.agent(server);
});

Thank you, Jamie

jcorkhill avatar May 11 '19 22:05 jcorkhill

@JamieCorkhill Yes please, await gor that one as well. I just forgot to await for it.

jonathansamines avatar May 12 '19 00:05 jonathansamines

@jonathansamines Thank you.

jcorkhill avatar May 12 '19 01:05 jcorkhill

@jonathansamines And I just wanted to make sure you know that I really appreciate your help and time. I have many test suites, and they've been taking a long time to compete, as well as all new test cases throwing a JavaScript heap out of memory error. I expect that will all be fixed now considering the heap should stop being bloated by open connections.

With the memory leak issue hindering me, I considered moving on with building the rest of the API without code coverage, so thank you very much for saving my TDD endeavor.

jcorkhill avatar May 12 '19 06:05 jcorkhill

This is working for us to get around this issue. Seems like something doesn't shutdown in the same tick as it should.

  afterAll(done => {
    setImmediate(done);
  });

re5et avatar May 14 '19 23:05 re5et

Thanks, @re5et I'll try it out.

jcorkhill avatar May 14 '19 23:05 jcorkhill

--maxWorkers=1 fixed all my issues

"scripts": {
    "test": "jest --watchAll --forceExit --detectOpenHandles --verbose --coverage --maxWorkers=1",

Start Server

beforeEach( ()=> { 
        server = require('../../../index');

Close Sever

afterEach( async () => {
        await server.close();

carlafranca avatar Jul 03 '19 19:07 carlafranca

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 supertest

jest.mock('express-session', () =>
  (options: SessionOptions) =>
    (req: Request, res: Response, next: NextFunction) => {
      next();
});

cif avatar Aug 08 '19 16:08 cif

@cif The issue is not usually caused by express-session itself, but rather by the particular store used. I usually rely on the default MemoryStore on tests, if it is really hard to properly shutdown the upstream connection to the store.

jonathansamines avatar Aug 08 '19 17:08 jonathansamines

@jonathansamines thanks for clarification there! We are indeed using a database store for sessions since we are using multiple instances of the service fronted by a load balancer (no sticky session)

cif avatar Aug 08 '19 17:08 cif

Hi, I'm also facing same issue, this is my test code

const request = require('supertest');
const app = require('../server');

describe('Hello', () => {
  test('succeeds with greetings', async (done) => {
    const res = await get('/api/hello')
      .expect(200);
    expect(res.body.greeting).toBe('hello API');
    done();
  });

});
function get(url) {
  const httpRequest = request(app).get(url);
  httpRequest.set('Accept', 'application/json')
  httpRequest.set('Origin', 'http://localhost:57305')
  return httpRequest;
}

And specifying a port while running my app like

var listener = app.listen(57305, function () {
  console.log('Your app is listening on port ' + listener.address().port);
});

all tests are getting passed but throws jestOpenHandle

--forceExit does solve my problem but still throws warning as discussed above and I'm trying to integrate travisCI for the first time but build is not getting complete. repo

going to run my build with --forceExit for now

Thank you

rohit-ambre avatar Aug 10 '19 12:08 rohit-ambre

I use --forceExit combined with --detectOpenHandles flags to be sure that this doesn't occur.

lukaswilkeer avatar Sep 08 '19 11:09 lukaswilkeer

I found out that if you are using process.env in any test it also won't exit.

WHAT?! that seems super weird -- do we know why?

jtushman avatar Sep 10 '19 20:09 jtushman

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.

tksilicon avatar Sep 20 '19 17:09 tksilicon

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 supertest

jest.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

Baystef avatar Sep 27 '19 21:09 Baystef

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.

elucidsoft avatar Nov 18 '19 03:11 elucidsoft

@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?

johncmunson avatar Nov 22 '19 13:11 johncmunson

@cif The issue is not usually caused by express-session itself, but rather by the particular store used. I usually rely on the default MemoryStore 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.

jlei523 avatar Nov 30 '19 08:11 jlei523

Weirdly, if I run the whole suite, Jest exists correctly.

jlei523 avatar Dec 02 '19 02:12 jlei523

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.

garyo avatar Dec 11 '19 16:12 garyo

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.

katlimruiz avatar Dec 27 '19 16:12 katlimruiz

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):

  • for each connection: https://github.com/nfroidure/whook/blob/master/packages/whook-http-server/src/index.ts#L104-L111
  • on server close: https://github.com/nfroidure/whook/blob/master/packages/whook-http-server/src/index.ts#L130-L133

nfroidure avatar Jan 06 '20 12:01 nfroidure

@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?

GrayStrider avatar Jan 28 '20 15:01 GrayStrider

afterAll(async () => {
	await sleep(500)
})

works too.

GrayStrider avatar Jan 28 '20 15:01 GrayStrider

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.

parthnaik avatar Feb 05 '20 08:02 parthnaik

@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 :(

dotku avatar Feb 29 '20 08:02 dotku

@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

dotku avatar Mar 05 '20 04:03 dotku

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.

iamjoross avatar Mar 05 '20 07:03 iamjoross

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()
});

shijiezhou1 avatar Mar 09 '20 13:03 shijiezhou1

how to do this with firestore based application

nikhilwairagade avatar Mar 17 '20 09:03 nikhilwairagade

It can be simplified to

afterAll(async () => {
  await new Promise(resolve => setTimeout(resolve, 500));
});

joticajulian avatar Jun 18 '20 06:06 joticajulian

I found simple open handle keeps case and that solution.

  • NodeJS v10.19.0
  • SuperTest v4.0.2
  • Jest v25.5.4

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.

soultoru avatar Jun 23 '20 05:06 soultoru

This is already commented by @alfonsoal1983.

soultoru avatar Jun 23 '20 05:06 soultoru

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();
  });

RaneWallin avatar Jun 25 '20 02:06 RaneWallin

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);
        });

marciobera avatar Jul 23 '20 14:07 marciobera

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.

katlimruiz avatar Jul 28 '20 02:07 katlimruiz

This is my working example, just in case it may help:

  • Node: v12.13.1
  • Supertest: 4.0.2
  • Jest: 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;
}

chrisvoo avatar Jul 29 '20 15:07 chrisvoo

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()
})

PawFV avatar Aug 25 '20 10:08 PawFV

Hello there, in my case I have the same problem and jest --detectLeaks --detectOpenHandles shows nothing

Screen Shot 2020-11-07 at 16 28 51

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

andrescabana86 avatar Nov 07 '20 19:11 andrescabana86

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 [email protected] 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

lukaswilkeer avatar Nov 09 '20 12:11 lukaswilkeer

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",
}

danday74 avatar Nov 16 '20 20:11 danday74

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.

alexey2baranov avatar Dec 04 '20 22:12 alexey2baranov

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(),
      });
  });

igorvolnyi avatar Dec 05 '20 17:12 igorvolnyi

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)
};

ZaidRehman avatar Jan 27 '21 09:01 ZaidRehman

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')
})

DanJFletcher avatar Jan 29 '21 20:01 DanJFletcher

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();
});

AugustoAmaral avatar Mar 15 '21 12:03 AugustoAmaral

@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 :)

fuciktomas avatar Mar 26 '21 04:03 fuciktomas

I added this condition around app.listen process.env.NODE_ENV !== 'test'. Works like charm

if (process.env.NODE_ENV !== 'test') {
	app.listen(port, ....)
}

Orlayhemmy avatar Apr 25 '21 05:04 Orlayhemmy

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();
  });
}); 

parasVohra avatar May 04 '21 03:05 parasVohra

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?

nfantone avatar May 13 '21 17:05 nfantone

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.

tperrinweembi avatar Jul 18 '21 08:07 tperrinweembi

  • Make sure you're not mocking process.nextTick.
  • If you use Jest's fake timers (either via config, or programatically), make sure you use 'legacy' (in Jest 27, the default is 'modern').
  • If you are not sure if Jest is mocking process.nextTick or not, add a console.log(toFake) here.

agalatan avatar Jul 25 '21 09:07 agalatan

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.

paulgrieselhuber avatar Sep 01 '21 07:09 paulgrieselhuber

Thanks @paulisloud, it did the trick ๐Ÿ‘

damienbry avatar Sep 21 '21 15:09 damienbry

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?

JohanAltamar avatar Sep 23 '21 22:09 JohanAltamar

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
})

raulfdm avatar Oct 08 '21 12:10 raulfdm

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

klh avatar Oct 16 '21 20:10 klh

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.

mishalalex avatar Oct 17 '21 20:10 mishalalex

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 =/

koderka2020 avatar Oct 23 '21 23:10 koderka2020

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 avatar Jan 02 '22 20:01 craigmiller160

@craigmiller160 Can you explain your comment on timeout() use? Jest has its own timeout, do these interrelate?

raarts avatar Jan 18 '22 20:01 raarts

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",.

DercilioFontes avatar Feb 10 '22 06:02 DercilioFontes

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!

bhavin-a avatar Feb 11 '22 06:02 bhavin-a