supertest: Open handle keeps Jest from exiting ( TCPSERVERWRAP)

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

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 159
  • Comments: 120

Commits related to this issue

Most upvoted comments

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.

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

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

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

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

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

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

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.

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?

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

works too.

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)

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!

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

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

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

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

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

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

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.

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

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

This worked well for me …


  import request from 'supertest'
  import app from '../../server'

  let server: Server
  let agent: request.SuperAgentTest

  beforeEach((done) => {
    server = app.listen(4000, () => {
      agent = request.agent(server)
      done()
    })
  })

  afterEach((done) => {
    server.close(done)
  })

However, I had to make the following changes to my server file:

if (process.env.NODE_ENV !== 'test') {
  app.listen(appConfig.port, () => {
    console.log(`Server running on port ${appConfig.port}`)
  })
}

export default app

The above change means that the server still starts when you run npm start but does not start the normal server when you are testing.

My npm start command from package.json is:

"scripts": {
  "test": "cross-env NODE_ENV=test jest --detectOpenHandles",
}

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

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.

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

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

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

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

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.

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

@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

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.

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

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 🤙

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

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!

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

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?

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.

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

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.

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

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

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.

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.

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

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.

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

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

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

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

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 😂