32

I am doing a basic end to end testing here, for the moment it's failing, but first I can't get rid of the open handle.

Ran all test suites.

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

  ●  TCPSERVERWRAP

      40 |     }
      41 |     return request(app.getHttpServer())
    > 42 |       .post('/graphql')
         |        ^
      43 |       .send(mutation)
      44 |       .expect(HttpStatus.OK)
      45 |       .expect((response) => {

      at Test.Object.<anonymous>.Test.serverAddress (../node_modules/supertest/lib/test.js:61:33)
      at new Test (../node_modules/supertest/lib/test.js:38:12)
      at Object.obj.<computed> [as post] (../node_modules/supertest/index.js:27:14)
      at Object.<anonymous> (app.e2e-spec.ts:42:8)
import { Test, TestingModule } from '@nestjs/testing'
import { HttpStatus, INestApplication } from "@nestjs/common";
import * as request from 'supertest'
import { AppModule } from '../src/app.module'

describe('AppController (e2e)', () => {
  let app: INestApplication

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

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

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

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(HttpStatus.OK)
      .expect('Hello World!')
  })

  it('mutation', async () => {
    const mutation = {
      query: `mutation Create($title: String!) {
        create(title: $title) {
          id,
          title
        }
      }`,
      variables: {
        title: 'Mon programme',
      },
    }
    return request(app.getHttpServer())
      .post('/graphql')
      .send(mutation)
      .expect(HttpStatus.OK)
      .expect( (response) => {
        expect(response.body).toBe({
          id: expect.any(String),
          title: 'Mon programme',
        })
      })
  })
})

Any idea what's blocking the test runner ?

Note that, as I am using NestJs, I shouldn't need to use the .end(done) method at the end of the test.

PS: apparently I have to much code on this question and I need to add some more details, but have no clue what I can say more.

A Mehmeto
  • 1,594
  • 3
  • 22
  • 37

9 Answers9

37

I still haven't found a perfect solution, but for the moment I went for this workaround :

jest --config ./test/jest-e2e.json --forceExit

The --forceExit option kill the openHandles somehow and unlock everything. Yet, I'm still looking for the "proper way" of handling that issue.

A. Maitre
  • 2,985
  • 21
  • 25
A Mehmeto
  • 1,594
  • 3
  • 22
  • 37
  • 1
    thank you so much I have spent hours trying to mock the setInterval() function causing problems but it didn't work. This is an easy, simple fix. – Patrick Sep 28 '21 at 20:15
  • 1
    I had --forceExit and --detectOpenHandles in my script after removed --detectOpenHandles it solved "test:e2e": "jest --config ./test/jest-e2e.json --forceExit" – Akif Kara Jun 01 '22 at 08:17
  • adding `--no-cache --watchAll` fixed for me, as answered by tonskton. my script: `jest --config ./test/jest-e2e.json --detectOpenHandles --watchAll --no-cache` – Manuvo Oct 30 '22 at 07:24
  • 1
    i don't think you need `--detectOpenHandles` in your script since that's more for debugging. I guess if you want to always have that output, then sure, but it's not necessary to get jest to exit. – hellatan May 10 '23 at 13:49
  • `--watchAll` fixes the issue for me too but then requires interaction. This isn't great in a CI/CD build though. – occasl Jul 11 '23 at 18:41
19

This is the problem right here

  it('/ (GET)', () => {
    return request(app.getHttpServer())
                  ^^^^^^^^^^^^^^^^^^^^^
      .get('/')
      .expect(HttpStatus.OK)
      .expect('Hello World!')
  })

The server isn't being closed and remains open after the test. You need to create a variable to reference the instance and close it after each test. I just spent a couple of hours trying to figure this out. And hope this helps anyone experiencing similar issues.

Here is an example of your code with my idea for a fix:

describe('AppController (e2e)', () => {
  let app: INestApplication
  let server: SERVER_TYPE

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile()

    app = moduleFixture.createNestApplication()
    await app.init()
    // Reference the server instance
    server = app.getHttpServer()
  })

  afterEach(async () => {
    await app.close()
    // Close the server instance after each test
    server.close()
  })

  it('/ (GET)', async () => {
    // Make the request on the server instance
    return await request(server)
      .get('/')
      .expect(HttpStatus.OK)
      .expect('Hello World!')
  })

Also, I noticed you're using beforeEach and afterAll. You're creating a new app each time for each test so I think that could also cause some issues for the HTTP server. I'm not certain on that though.

import { Test, TestingModule } from '@nestjs/testing'
import { HttpStatus, INestApplication } from "@nestjs/common";
import * as request from 'supertest'
import { AppModule } from '../src/app.module'

beforeEach(() => {
  ...
})

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

describe('tests', () => {
  ...
})

But, that's just my preference, up to you. :)

UPDATE: Meant to use beforeEach not beforeAll because we need to close the server before EACH test, not a global setup and teardown.

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.

7

You are re-creating the whole app again beforeEach, but tearing it down only in afterAll, which means you are probably leaking some memory along the way. You are assigning a new instance to the app variable, but there are most likely hidden references that prevent the garbage colletor from clearing the previous instance - like the reference that the request function got.

Change beforeEach to beforeAll and you should be good to go.

Papooch
  • 1,372
  • 11
  • 17
  • Thank for the answer. But unfortunately it has no effect. – A Mehmeto Jul 19 '21 at 09:25
  • @AMehmeto hm, strange, are you running multiple tests in parallel or just one file? – Papooch Jul 19 '21 at 10:04
  • Just one file only. – A Mehmeto Jul 20 '21 at 04:56
  • @AMehmeto my second guess is that something is happening within your app that keeps it from exiting. Have you [read through this thread](https://github.com/visionmedia/supertest/issues/520#issuecomment-469044925), expecially the linked comment? – Papooch Jul 20 '21 at 07:50
5

To anyone who still encountering error even though you already close the connection and its and intermittent error you can try to add --no-cache --watchAll. Below is the full syntax:

"test": "jest --watchAll --no-cache --detectOpenHandles"

tonskton
  • 51
  • 1
  • 4
  • I don't know why, but this is the only thing that worked for me, `--no-cache --watchAll` did the trick. `--watchAll` is not optional which makes no sense to me, however I'm happy it works, I am using mongoose and mongodb-memory-server – Manuvo Oct 30 '22 at 07:21
1

Had the same issue.

    "test:e2e": "jest --config ./test/jest-e2e.json --no-cache --detectOpenHandles",

worked fine for me

AntiHero
  • 21
  • 1
  • 1
  • but it seems like not the best option due to: "The cache should only be disabled if you are experiencing caching related problems. On average, disabling the cache makes Jest at least two times slower." from official docs – AntiHero Jan 22 '23 at 23:01
0

Instead of it try using test and pass done as a parameter and call that. This worked for me.

test('mutation', async (done) => {
    const mutation = {
      query: `mutation Create($title: String!) {
        create(title: $title) {
          id,
          title
        }
      }`,
      variables: {
        title: 'Mon programme',
      },
    }
    const response = request(app.getHttpServer())
      .post('/graphql')
      .send(mutation)
     expect(response).to.be(HttpStatus.Ok)
     done()
  })
ShadyAmoeba
  • 527
  • 1
  • 4
  • 15
0

// on each test

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

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 02 '22 at 10:21
0

Answer from Toomuchrice4u has helped me. I had a logout method in one of the services that component uses, so I called it in afterEach, like this:

afterEach(async () => {

await userService.logout();

});

fadingbeat
  • 355
  • 3
  • 16
0

Also check your package.json file in the scripts section to find the test:e2e key. Check the value of it and remove the parameter --detectOpenHandles. the key-value pair of script can be as follow: "test:e2e": "jest --config ./test/jest-e2e.json --forceExit"

Ramin Ar
  • 1,213
  • 13
  • 10