I have a Jest test suite using Supertest, containing around 300 tests. Most of the time they all pass. However, sometimes a test fails, which appears to be completely random.
It fails with the following message:
read ECONNRESET
Nothing else is shown, no stack trace nor anything else. I have tried:
The tests are interacting with a Postgres db, as well as Redis. The logs of both of these, show no errors
Thinking that it could be some sort of uncaught Node.js error I added the following, which also didn't catch anything:
process
.on('unhandledRejection', (reason, p) => {
console.error(reason, 'Unhandled Rejection at Promise', p);
})
.on('uncaughtException', err => {
console.error(err, 'Uncaught Exception thrown');
process.exit(1);
});
I am using:
I have found that there are actually 2 issues with supertest
. One of them is related to Supertest's internals and one is related to the recommended usage of Supertest:
Issue #1 - asynchronous startup
When starting up a server with supertest
, it is recommended by the supertest
docs to repeat the following pattern for each test:
request(app)
.get('/user')
.status(200);
The problem with this is that supertest
is internally considering app.listen()
to be a "synchronous" event, while really it is "asynchronous". This can occasionally create a race condition, where the request reaches the server, before it is ready to accept any. This issue was already reported in this Github issue, but despite a good amount of upvotes, there seems to be no sign of this getting fixed.
Luckily, it is pretty easy to workaround this issue, by starting the server in advance and manually controlling its lifecycle:
let server;
beforeAll((done) => {
server = app.listen(0, '127.0.0.1', done);
});
afterAll(() => {
server.close();
});
test('Should get user', async () => {
await request(server)
.get('/user')
.expect(200);
});
This way, the race condition mentioned above is eliminated and the READ ECONNRESET
error should disappear.
Issue #2 - incorrect port selection
In addition to the READ ECONNRESET
error, I also sometimes encountered a 400
status code with the WebSockets request was expected
message.
It turns out that by default supertest
picks a random free port to start the server on. However, supertest
looks for a free port on 0.0.0.0
, while other services might use ports on a different host: 127.0.0.1
.
An example of this is the VSCode
debugger. Because, it runs on a different host, supertest
might select the same port the debugger is listening on. This results in the error above, as the request is now send to the debugger as opposed to the server
.
The solution is to explicitly specify the host
when starting up the server as mentioned in Issue #1
above:
server = app.listen(0, '127.0.0.1', done);
Because the hostname
is now supplied, supertest
will only use free ports on that hostname
.
This "port selection issue" was found by the same person who opened the Github issue mentioned above here.