ladjs / supertest

🕷 Super-agent driven library for testing node.js HTTP servers using a fluent API. Maintained for @forwardemail, @ladjs, @spamscanner, @breejs, @cabinjs, and @lassjs.
MIT License
13.75k stars 758 forks source link

Promises makes jest test time out #697

Open olaven opened 3 years ago

olaven commented 3 years ago

Hello 👋 Thank you for maintaining this project.

I believe I am experiencing a bug where awaiting a call to get response causes timeout.

Minimal example:

it("TIMEOUT when awaiting", async () => {

    await supertest(app)
        .get("/users/me")
});

it("TIMEOUT when using .then", (done) => {

    supertest(app)
        .get("/users/me")
        .then(response => {

            done();
        });
});

it("WORKING when not using promises at all", () => {

    supertest(app)
        .get("/users/me");
});

The two first tests are failing with the following message: Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.Error:

Please let me know if I have left out any useful information.

All the best! Olav

tosiek88 commented 3 years ago

Are you getting same behavior with async/await ?

olaven commented 3 years ago

@tosiek88 Yes I am. I believe the first test shows this. Or are you referring to something else? 🙂

bogdanned commented 3 years ago

same here

BalazsSevecsek commented 3 years ago

I have the same problem with async/await.

Using the following versions: "jest": "26.6.3", "supertest": "3.4.2",

codingthat commented 3 years ago

@olaven @bogdanned @BalazsSevecsek Does there happen to be a runtime error in the app itself (not the tests) in your cases? Cf. https://github.com/mochajs/mocha/issues/4632

mkimbo commented 3 years ago

same problem here..jest not working with async/await with supertest..without async await all tests pass even when they supposed to fail

luukvhoudt commented 3 years ago

@olaven when running the test without promises at all, I also get the following warning:

A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.

Besides that I can also confirm that the bug also occurs when using the agent:

const agent = supertest.agent(app);

it("TIMEOUT when awaiting with agent", async () => {

    await agent.get("/users/me")
});

it("TIMEOUT when using .then", (done) => {

    agent
        .get("/users/me")
        .then(response => {

            done();
        });
});

it("WORKING when not using promises at all", () => {

    agent.get("/users/me");
});
aderchox commented 2 years ago

I can't say for sure if my issue is the same, but I have a similar issue. In my case, I always get the below warning from Jest when I use the supertest request's post method even without using any async/await/promises.

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.

The test code is like this:

    it("POST /api/notes adds the note", (done) => {
        request(app)
            .post("/api/notes")
            .send({
                "content": "some note",
                "notebookId": "0"
            }).expect(200)
            .expect(resp => resp.body.content === "some note")
            .then(_ => {
                request(app)
                .get("/api/notes")
                .expect(resp => resp.body.length === notes.length + 1);
                done();
            }).catch(err => done(err));
    });
Tokimon commented 2 years ago

Hi, I have encountered the same problem and I have narrowed it down to when you simply have a promise that has been put on the call stack.

So these don't pose a problem:


it('FIRST', () => Promise.resolve(true));

it('SECOND', () => Promise.resolve(true));

but as soon as you introduce a timeout in the mix, it breaks (the second test times out):


it('FIRST', () => new Promise((r) => setTimeout(r)));

it('SECOND', () => new Promise((r) => setTimeout(r)));

Even if I try to follow the way the documentation describes and use .resolves, the problem persists:


const asyncfn = (message: string) => new Promise((r) => setTimeout(() => r(message)));

it('FIRST', () => expect(asyncfn('first')).resolves.toBe('first'));

it('SECOND', () => expect(asyncfn('second')).resolves.toBe('second'));
iliaskarim commented 2 years ago

I also experienced this issue.

premekholasek commented 2 years ago

Same here.

1forh commented 2 years ago

same here! this is happening for me too

digzom commented 2 years ago

Apparently, in my case, I can't use ES6 form to import/export modules.

So, I create a file called testServer.ts with the same code as my default server (server.ts), but using module.exports instead export default.

const app = express()

app.use(cors())
app.use(helmet())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(express.json())
app.use(routes)

module.exports = app

In my test file I used require to import the test server, and I used async/await syntax.

import "reflect-metadata"
import request from "supertest"
import AppDataSource from "../../../database/dataSource"
const app = require("../../../utils/testServer")

describe("getAllProductsController", () => {
  describe("GET /products", () => {
    beforeAll(async () => {
      await AppDataSource.initialize()
    })

    afterAll(async () => {
      await AppDataSource.destroy()
    })
    it("should return 200 and valid response if request param list is empty", async () => {
      const response = await request(app).get("/api/products")
      console.log(response.status)
      expect(response.status).toEqual(200)
    })
  })
})

I still intend to improve this, but for now it has given me peace of mind.

mojadev commented 2 years ago

I'm not sure this is related to the reported or known behaviour, but looked similar in my case and might help others stumbling over this.

For me jest.useFakeTimers() caused supertest to break existing test cases by timing out. I didn't have time to dig into the cause of this, but given that fakeTimers is quite invasive I wouldn't consider this a supertest/superagent bug.

miparnisari commented 2 years ago

Hi, I have encountered the same problem and I have narrowed it down to when you simply have a promise that has been put on the call stack.

So these don't pose a problem:

it('FIRST', () => Promise.resolve(true));

it('SECOND', () => Promise.resolve(true));

but as soon as you introduce a timeout in the mix, it breaks (the second test times out):

it('FIRST', () => new Promise((r) => setTimeout(r)));

it('SECOND', () => new Promise((r) => setTimeout(r)));

Even if I try to follow the way the documentation describes and use .resolves, the problem persists:

const asyncfn = (message: string) => new Promise((r) => setTimeout(() => r(message)));

it('FIRST', () => expect(asyncfn('first')).resolves.toBe('first'));

it('SECOND', () => expect(asyncfn('second')).resolves.toBe('second'));

@Tokimon these work for me..

rviktor87 commented 2 years ago

I had a similar problem. I needed to wrap the await call within try/catch block. In the catch block you can get the error and you should use the done() in the catch block as well.

LoganBarnett commented 1 year ago

On a project of mine, I was able to work around this by ensuring listen was not called as part of importing the Express app that is passed to request. Essentially, the listen call is done in index.js (our entrypoint) and the Express app is created in app.js. index.js imports app.js. The tests only ever import app.js.

hariskamran commented 1 year ago

Hello, I was facing the same problem. The problem was require('express'). This returns the defaults express app object and contains none of the configuration for your app.

Change it as follows: const app = require('./app');

This will now run jest with your app.ts server and there won't be a timeout.

1nstinct commented 1 year ago

I'm not sure this is related to the reported or known behaviour, but looked similar in my case and might help others stumbling over this.

For me jest.useFakeTimers() caused supertest to break existing test cases by timing out. I didn't have time to dig into the cause of this, but given that fakeTimers is quite invasive I wouldn't consider this a supertest/superagent bug.

oh wow. You saved me tons of time! Thanks

oliveirarthur commented 9 months ago

I managed to get it working by adding a finally block:

    test('endpoint works', (done) => {
        void supertestInstance
            .get('my/endpoint')
            .expect(200)
            .then((res: any) => {
                // expect stuff
            }).finally(() => done());
    });
hylickipiotr commented 8 months ago

I'm not sure this is related to the reported or known behaviour, but looked similar in my case and might help others stumbling over this.

For me jest.useFakeTimers() caused supertest to break existing test cases by timing out. I didn't have time to dig into the cause of this, but given that fakeTimers is quite invasive I wouldn't consider this a supertest/superagent bug.

That's correct. In my case, the fake timer also caused the problem. After using the real time, the problems disappeared.

heeboA commented 4 days ago

A bit late to the party, but I found https://medium.com/@Alfonso_Ghislieri/fixing-timeout-errors-when-mocking-date-in-jest-using-supertest-5adf939ff22b which fixes the issue. Relevant info:

Yet, some of these [things mocked by useFakeTimers] are needed for Supertest to not timeout, namely: setImmediate and nextTick. This makes for a quick fix:

jest.useFakeTimers({ doNotFake: ["nextTick", "setImmediate"] });

This worked for me, as I don't need to mock either of the above.