95

I'm using JEST for unit testing my express routes.

While running the yarn test all my test case are getting passed, but I'm getting an error

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.

I used async & done, but still it throws the above error.

Below is my spec code. Please help

routes.spec.ts

const request = require('supertest');
describe('Test the root path', () => {
  const app = require('./index');

  test('GET /gql/gql-communication-portal/release-notes', async (done) => {
    const response = await request(app).get('/gql/gql-communication-portal/release-notes');
    expect(response.status).toBe(200);
    done();
  });
});
skyboyer
  • 22,209
  • 7
  • 57
  • 64
Aw3 Sol
  • 1,063
  • 2
  • 8
  • 9
  • `app = require('./index')` does this start the server listening on a port? ideally your server should be set up so it can be imported without actually starting it, which is what you want to do in this instance. It might also resolve your issue. – lecstor Mar 05 '19 at 23:04
  • must remember to look at "asked" date.. – lecstor Mar 06 '19 at 00:04
  • Because you use a promise in the code snippet you provide, it looks like you don't need the `done()` callback in your `GET` test. I have a similar scenario where I just use `async` and `await`. [Here](https://jestjs.io/docs/asynchronous#callbacks) is where the Jest documentation talks about callbacks as an alternative to `async` `await` promises. Not directly related to your question, but perhaps useful for future readers like myself. – M Stefan Walker Feb 21 '23 at 16:27

13 Answers13

91

My problem was solved by this code:

beforeAll(done => {
  done()
})

afterAll(done => {
  // Closing the DB connection allows Jest to exit successfully.
  mongoose.connection.close()
  done()
})
Saleh Rahimzadeh
  • 1,261
  • 12
  • 10
27

I have added this line to package.json

It worked for me

jest --runInBand --detectOpenHandles --forceExit
17

For me it was a different issue I was using supertest to test routes itself so I had to close the connection to the server itself.

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

You can start server in beforeAll block and close in after all:

beforeAll(() => {
   server = app.listen(someRandomNumberHere); // Random number is needed to avoid using same port in different tests if you run in parallel
})

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

If this is not the case for you this issue might have something for you

Black Mamba
  • 13,632
  • 6
  • 82
  • 105
14

On my side, I just separate app.listen() from my app. So with express, your app finish with an export.

// index.js
module.exports = app;

And just create another file to listen the port.

// server.js
const app = require('./index')
app.listen(...)

And if you import just the index (app index.js) in your tests, it should work with no extra config. Of course your need to adjust the start of your express app. It should use now server.js.

Guillaume
  • 271
  • 2
  • 8
  • 2
    Thank you! do you know why moving app.listen outside on the file works? – nkhil Feb 11 '21 at 19:50
  • This was the case for me thanks. I'm using the request function from supertest and it doesn't need the server to run so I separated the app.listen from it. Also here's a complete example of such a separation for future readers: https://tutorialedge.net/typescript/creating-rest-api-express-typescript/#our-appts-file – aderchox Dec 16 '21 at 23:09
13

I was having the same issue but in my package.json file i added "test": "jest --detectOpenHandles" and ran npm test --detectOpenHandles. I didn't get the error message this time. Maybe you can try doing that.

atymic
  • 3,093
  • 1
  • 13
  • 26
  • 1
    I guess `--runInBand`, [which is set with `--detectOpenHandles` implicitly](https://jestjs.io/docs/en/cli#detectopenhandles), fixed the issue in that case. – yunabe Sep 22 '19 at 01:55
  • 37
    This shoud be a suggestion to detect what is causing the issue but not to resolve that. – Raphael G. Frantz Oct 03 '20 at 22:28
  • 9
    This just suppresses the error message because it switches on diagnostics - it does nothing to solve the error, and will not allow the tests to exit successfully. – Chris Halcrow Mar 16 '21 at 02:59
10

Adding

jest.useFakeTimers();

at the beginning of the test suite fixed it for me.

Might come from timers defined in components part of the render (like throttled buttons, mocks etc..).

aquinq
  • 1,318
  • 7
  • 18
  • 1
    Geez wow. Thanks for this. If jest/enzyme would simply just unmount a functional component correctly I feel this wouldn't even be necessary but thanks for saving me hours regardless. I put in an three dot ellipses, fully tested that the setInterval ran an cleared in the UI. Wrote the tests and still can't get coverage on the return which clears it. – Erik Grosskurth Feb 22 '22 at 15:02
  • unbelieveable. This actually worked. – john k Aug 28 '23 at 15:05
8

This worked for me

const mongoose = require('mongoose');
    afterAll(async(done) => {
  // Closing the DB connection allows Jest to exit successfully.
  try {
    await mongoose.connection.close();
    done()
  } catch (error) {
    console.log(error);
    done()
  }
  // done()
})
olawalejuwonm
  • 1,315
  • 11
  • 17
  • 1
    How exactly did that work? in `afterAll` you can either return a promise or call the `done` callback. you can't do both, it'll throw an error! – Ammar Oker Jul 13 '22 at 08:42
3

For Firebase I had to call cleanup()

import {
    assertFails,
    assertSucceeds,
    initializeTestEnvironment,
    RulesTestEnvironment,
} from "@firebase/rules-unit-testing";
import { doc, setDoc } from "firebase/firestore";

it('creates a new user document in firebase', async () => {
    const testEnv = await initializeTestEnvironment({
        projectId: "appname-test",
        firestore: {
            host: 'localhost',
            port: 8088
        }
    });

    const alice = testEnv.authenticatedContext("alice");

    await assertSucceeds(setDoc(doc(alice.firestore(), "users", "alice"), {
        fname: "Alice",
        lname: "Wonderland",
        dob: "18/01/1999",
        email: "alice@example.com"
    }));

    return await testEnv.cleanup();
});
Umair A.
  • 6,690
  • 20
  • 83
  • 130
3

You can try this one

"test": "jest --runInBand --force-exit"

  • 1
    Thank you for this code snippet, which might provide some limited, immediate help. A [proper explanation](https://meta.stackexchange.com/q/114762/349538) would greatly improve its long-term value by showing why this is a good solution to the problem and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you’ve made. – jasie Feb 10 '22 at 09:09
1

I was running into the same issue but, I was using the pg module with my NodeJS Express app. I want to post this for those using this stack as well if it helps them.

Essentially, supertest creates a server connection, which some people might get the TCPSERVERWRAP error because it doesn't get closed, regardless whether I use async/await or the jest done callback. So, this has to be closed after each test. On top of that, the database connection remains open so I mocked it.

Closing the server connection and mocking pg together solved the error for me.

products.tests.ts

import request from 'supertest'
import { Pool } from 'pg'
import app from '../app'
import type { Server } from 'http'

jest.mock('pg')

const { ROUTE_VERSION = '/v1' } = process.env
const route = (path: string) => [ROUTE_VERSION, path].join('')
const pool = new Pool()
let server: Server

beforeEach(() => {
   server = app.listen(4000)
})

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

describe('GET /products', () => {
   it('returns array of products', async () => {
      await request(server)
         .get(route('/products'))
         .expect(200)
         .expect((res) => {
            expect(pool.query).toBeCalledTimes(1)
            expect(res.body).toBeInstanceOf(Array)
            expect(res.body).not.toHaveLength(0)
         })
   })
})

UPDATE: Meant to use beforeEach and afterEach to close the server after EACH test, otherwise, it still remains open like before.

UPDATE 2: Using async/await otherwise, it will always pass because request is asynchronous and doesn't complete unless you wait for it to finish.

1

I got the same problem due to not closing the database connection after all the test cases runs. It can be done using jest.config.js and adding a new file to root folder which will globally close the database connection after all the test cases.

In jest.config.js file add the following to the module.exports ,

module.exports = {
   setupFilesAfterEnv: ['<rootDir>/src/runAfterAllTests.ts'],
}

Then add runAfterAllTests.ts to src folder.

runAfterAllTests.ts will globally close the db connection. For mongodb it should be something like this.

import { client } from './database/connect';

global.afterAll(async () => {
  await client.close();
});

This will close the db connection after all the tests and fix the issue.

Isuru Maldeniya
  • 151
  • 1
  • 7
0

In NestJs apps, where you have something like

app = moduleFixture.createNestApplication();
await app.init();

Just add this in your test file

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

You can as well use async

afterAll(async() => {
    await app.close()
})
Obinna Nnenanya
  • 1,530
  • 14
  • 15
0

My problem is that I use Sequilize ORM and I forgot to close it at the end of testing inside the afterAll function callback.

This is my code:

afterAll((done) => {
    MatrixTableHeaderCol.destroy({
        where: {},
        force: true
    }).then(() => {
        done();
    });
});
Tri Dawn
  • 540
  • 6
  • 11