Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open handle keeps Jest from exiting ( TCPSERVERWRAP) #520

Open
lucianonooijen opened this issue Nov 3, 2018 · 120 comments
Open

Open handle keeps Jest from exiting ( TCPSERVERWRAP) #520

lucianonooijen opened this issue Nov 3, 2018 · 120 comments

Comments

@lucianonooijen
Copy link

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
@jonathansamines
Copy link

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

@lucianonooijen
Copy link
Author

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

@danielantelo
Copy link

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

@jonathansamines
Copy link

jonathansamines commented Nov 5, 2018

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

@danielantelo
Copy link

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!

@jonathansamines
Copy link

jonathansamines commented Nov 6, 2018

@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

@danielantelo
Copy link

@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

@jonathansamines
Copy link

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

@danielantelo
Copy link

danielantelo commented Nov 7, 2018

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 😂

@skykanin
Copy link

skykanin commented Nov 8, 2018

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)

@danielantelo
Copy link

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

@jonathansamines
Copy link

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

@kramerc
Copy link

kramerc commented Nov 14, 2018

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

@jalooc
Copy link

jalooc commented Dec 5, 2018

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.

@ae-lexs
Copy link

ae-lexs commented Jan 10, 2019

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)

@alfonsoal1983
Copy link

alfonsoal1983 commented Jan 22, 2019

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

@marcospgp
Copy link

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

@danieldaeschle
Copy link

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

@sbonami
Copy link

sbonami commented Feb 21, 2019

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

@buuug7
Copy link

buuug7 commented Feb 26, 2019

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

@yss14
Copy link

yss14 commented Mar 3, 2019

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.

@buuug7
Copy link

buuug7 commented Mar 7, 2019

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

@sharmaar12
Copy link

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?

@angristan
Copy link

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

@yss14
Copy link

yss14 commented Mar 30, 2019

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

@milenkovic
Copy link

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.

@eithermonad
Copy link

eithermonad commented May 7, 2019

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.

@eithermonad
Copy link

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

@eithermonad
Copy link

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

@jonathansamines
Copy link

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

@raarts
Copy link

raarts commented Jan 18, 2022

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

@DercilioFontes
Copy link

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

@Bhavin789
Copy link

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!

@Luichix
Copy link

Luichix commented Feb 27, 2022

Hello, I was doing the following test of the code, since I am following this course https://fullstackopen.com/en/part4/testing_the_backend

const mongoose = require('mongoose')
const supertest = require('supertest')
const app = require('../app')
const api = supertest(app)

test('notes are returned as json', async () => {
    await api
        .get('/api/notes')
        .send()
        .expect(200)
        .expect('Content-Type',/application\/json/)
})

afterAll(() => {
    mongoose.connection.close()
})

which the message is generated: Jest did not exit one second after the test run has completed.

I decided to continue adding tests to the file since I did not find a solution and I came across the following note.

NB: When running a single test, the mongoose connection might stay open if no tests using the connection are run. The problem might be due to the fact that supertest primes the connection, but Jest does not run the afterAll portion of the code.

so that I am running 2 additional tests on the same file that involve modifying the database and it no longer shows me the jest message.

Best regards

@codenoobforreal
Copy link

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 think this one is the answer!
you do not need app to start in the jest testing env.

@nareshy-quisitive
Copy link

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

This solution worked for me.

@tin-pham
Copy link

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

Thanks, this work for me

@jameskentTX
Copy link

jameskentTX commented Apr 30, 2022

I eliminated my open handle errors by restarting my pc.

But restart in my case helps for only one run of tests only. Then errors are back.

@emadunan
Copy link

emadunan commented May 13, 2022

This worked!

package.json

"scripts": {
    "test": "dotenv -e .env.test -- jest -i"
  }

Note: That I have omitted --detectOpenHandles option. if you added it, it will raise ERROR "Jest has detected the following 1 open handle potentially keeping Jest from exiting...".

server.ts

// Listen to requests
const applistener = app.listen(PORT, () => {
    console.log(`Web server is running on port ${PORT}.`);
});

export default applistener;

test-file1.ts

import supertest from "supertest";
import app from "../../server";

const request = supertest(app);

describe("Books Endpoints Tests", () => {

    afterAll(() => {
        app.close()
    })

    describe("GET /books", () => {
        test("Return array of books", async () => {
            const response = await request.get("/books");
            expect(response.status).toBe(200);
        });
    });
});

test-file2.ts

import supertest from "supertest";
import app from "../../server";

const prisma = new PrismaClient();
const request = supertest(app);

describe("Expression Endpoints Tests", () => {

    afterAll(async () => {
        await prisma.$executeRaw`TRUNCATE TABLE expressions CASCADE`;
        await prisma.$executeRaw`ALTER SEQUENCE expressions_id_seq RESTART WITH 1`;
        app.close();
    });

    describe("POST /expressions", () => {
        test("Create new expression and return it", async () => {
            const response = await request.post("/expressions").send({
                "textu": "exp1",
                "textf": "exp1.1",
                "definition": "exp-def"
            });
            expect(response.status).toBe(201);
            expect(response.body.data.textf).toBe("exp1.1");
            expect(response.body.data.definition).toBe("exp-def");
        });
    });
});

Sounds weird, but this is how it worked with me!

@Norfeldt
Copy link

Norfeldt commented May 23, 2022

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

Tried this out but ran into a couple of issues that I think is caused by this:

  1. Since it exit with 0 it will pass CI like github actions even if some test fails 😱
  2. It will no longer be possible to use the flag --watchAll

@uchepercynnoch
Copy link

uchepercynnoch commented May 24, 2022

In my case, all I did to solve the problem, was install weak-napi as a dev dependency and then set detectLeaks and detectOpenHandles to true in my jest config file like so:

module.exports = {
 preset: "ts-jest",
 testEnvironment: "node",
 verbose: true,
 bail: 1,
 testTimeout: 60000,
 detectOpenHandles: true,
 detectLeaks: true,
};

@ProvokerDave
Copy link

@nfroidure Thanks for doing this digging, my excellent internet friend! I am no longer getting the

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

  ●  bound-anonymous-fn

nastygram warning at the end of my functional tests after I added the DESTROY_SOCKETS=true environment variable to my jest command line:

NODE_ENV=development DESTROY_SOCKETS=true jest --no-cache --runInBand --passWithNoTests --detectOpenHandles --testPathPattern=...

I find that I still have an unaccounted-for 30 second pause after the tests run unless I add --forceExit to the command line however. I would like it to exit cleanly immediately without needing that, but no longer having the warnings at the end of the run is good enough for government work and solves a multi-year mystery for me.

Generally speaking, there are multiple unacceptable pauses I am seeing when running this Express+Supertest combo that make the test suites lag horribly. I would like all the suites to finish in a second or two, but it takes more like 20 seconds to complete and most of that time is spent before any test cases have yet run. What's the holdup?

Thanks again, you improved my quality of life!

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

@bpinazmul18
Copy link

just remove --detectOpenHandles from command.
prev: "test": "jest --forceExit --detectOpenHandles --maxWorkers=1 --verbose"
next: "test": "jest --forceExit --maxWorkers=1 --verbose"

@treoden
Copy link

treoden commented Jun 30, 2022

I was facing the same issue and it turned out the MySQL connection stays active after finish the test and prevent Jest from closing.
The HTTP server was closed properly.

So I think this can be your mongoose db connection manager?

Thanks

@petersg83
Copy link

petersg83 commented Jul 11, 2022

Hello ! I just had the problem and fixed it (I think).
I fixed it this way:

I made the http server starting listening (just did server.listen()) before passing the server to supertest (instead of letting supertest making it listening), and I close/destroy the server myself afterwards.

@whizyrel
Copy link

whizyrel commented Sep 9, 2022

just remove --detectOpenHandles from command. prev: "test": "jest --forceExit --detectOpenHandles --maxWorkers=1 --verbose" next: "test": "jest --forceExit --maxWorkers=1 --verbose"

For some reason, removing --detectOpenHandles worked in two different contexts. This error

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

mostly occurs when .send({...}) is called.

await request(app.getHttpServer())
      .post('/load/tag/index/create')
      .send(payload);

@laygir
Copy link

laygir commented Sep 10, 2022

I was having similar issue with Mongoose and getting TLSWRAP with an open handle for a test looks as simple as this:

beforeAll(async () => {
  await mongoose.connect(MONGODB_URL, MONGODB_OPTIONS);
});

describe('Sum', () => {
  it('should return 2 for 1 + 1', async () => {
    expect(1 + 1).toBe(2);
  });
});

afterAll(async () => {
  await mongoose.disconnect();
});

Simply connecting and disconnecting from Mongoose was causing issues. 👇

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

  ●  TLSWRAP

       9 |
      10 | beforeAll(async () => {
    > 11 |   await mongoose.connect(MONGODB_URL, MONGODB_OPTIONS);
         |                  ^
      12 | });
      13 |
      14 | describe('Sum', () => {

      at resolveSRVRecord (node_modules/mongodb/src/connection_string.ts:80:7)

I've tried pretty much all suggestions here.
Adding a setTimeout in afterAll did not work at all. But adding setTimeout in beforeAll just before the mongo connection did work, like so:

beforeAll(async () => {
  await new Promise((res) => {
    setTimeout(res, 1);
  });
  await mongoose.connect(MONGODB_URL, MONGODB_OPTIONS);
});

☝️ Notice the duration for setTimeout does not have to be more than 1 ms. This smells like a process nextTick issue somewhere. Meanwhile Mongoose website screams to not use Jest with Mongoose apps and not to use global setups and not to use fake timers etc... But of course I didn't listen and thought what could go wrong. Now ended up wasting hours..

Solution

I am not fan of using timers to fix things, so for now I am moving forward with just calling disconnect in beforeAll to disconnect just in case there is anything there, like so:

beforeAll(async () => {
  await mongoose.disconnect();
  await mongoose.connect(MONGODB_URL, MONGODB_OPTIONS);
});

I am hoping this holds for me and helps anyone else out there 🤙

@mwibutsa
Copy link

I had the same issue. I was providing both --detectOpenHandles and --forceExit, then I removed --detectOpenHandles and now it's working. Though, I would love to see the best way to fix this.

@rolandm2017
Copy link

I too would like to see a great way to fix this. From someone who has a complete understanding of why it happens. This person could describe the optimal and clean solution.

@ASHoKuni
Copy link

For me, it was solved with a condition for adding the listener and mongoose

  1. For app listener i added below code in index.js or server.js or app.js for listener app .
if (process.env.NODE_ENV !== 'test') {
  app.listen(port,hostname,() => {
  })
}
  1. For mongoose issue
    Jest has detected the following 1 open handle potentially keeping Jest from exiting:
    ● TLSWRAP
// first import mongoose in every *.test.js file 

  const mongoose = require('mongoose');

// then call method afterAll to disconnect mongoose.

afterAll(async () => {
    await mongoose.disconnect(); 
  }); 

  1. I added below command in package.json file
"scripts": {
 "test": "NODE_ENV=test NODE_TLS_REJECT_UNAUTHORIZED=0 jest --reporters default jest-stare --coverage --detectOpenHandles --runInBand --testTimeout=60000 --config ./jest.config.js",
}
  1. I set jest.config.js config file like.
module.exports = {
    testEnvironment: 'node',
    moduleFileExtensions: [
      'ts',
      'tsx',
      'js',
      'jsx',
      'json',
      'node'
    ],
    // setupFiles: ['<rootDir>/test-teardown-globals.js'],
    verbose: true,
    coverageDirectory: "/Users/xxx/Documents/nodejs/test/",
    coverageReporters: ["html","text"],
    coverageThreshold: {
    global: {
            "branches": 100,
            "functions": 100,
            "lines": 100,
            "statements": 100
          }
        }
};

  1. I run using below command
    npm run test

ChadFusco added a commit to RFP2210-SDC/API-RatingsReviews that referenced this issue Dec 30, 2022
@gelouko
Copy link

gelouko commented Jan 7, 2023

@jonathansamines 's first solution worked for me.

Here is an working example with async/await syntax of it:

beforeEach(async () => {
    const module = await Test.createTestingModule({
      imports: [AppModule], // your app module
    }).compile();

    app = module.createNestApplication();
    server = await app.listen(5325);
    await app.init();
  });

it(`do something`, async () => {
    const result = await request(server).post('/cats').send(catDto);

    expect(result.status).toBe(201);

    const { body } = result;

    expect(body.name).toEqual(catDto.name);
    expect(body.age).toEqual(catDto.age);
    expect(body.breed).toEqual(catDto.breed);
  });

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

@james246
Copy link

I was having this issue and eventually traced it back to an issue with Jest and its useFakeTimers option, which I was using to mock the date.

If you are using jest.useFakeTimers(), to mock the date/time, try an alternative such as mocking manually as there appears to be an issue with jest's new/"modern" implementation of fake timers that doesn't play nice with async.

@jherax
Copy link

jherax commented Aug 13, 2023

I tried all candidate solutions mentioned here. Thanks all for share. It seems that there is an odd behavior with Mongoose and an internal nextTick call, so finally I solved the problem by adding a nextTick() before calling the POST.

import type {Server} from 'http';
import {agent as request} from 'supertest';

let server: Server;

beforeAll(async () => {
  server = await initServer();
});

afterAll(async () => {
  server.close();
});

it(`POST "${v1}/products" responds with the entity created`, async () => {
    const payload: IProduct = { /* ... */ };

    // odd fix for Jest open handle error
    await Promise.resolve(process.nextTick(Boolean));

    const reply = await request(server).post(`${v1}/products`).send(payload);

    expect(reply.statusCode).toEqual(200);
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests