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.84k stars 759 forks source link

Supertest binding to different ephemeral port on every request? #489

Open mattdeluco opened 6 years ago

mattdeluco commented 6 years ago

While testing with supertest I have noticed that after a test run netstat reports a connection in TIME_WAIT state for every call (get, post, etc.) made with a supertest agent. This can become a problem for me doing frequent and larger test runs - my system actually runs out of either file handles or ports (not sure which.)

My app binds to port 3000 and it's my understanding that if the app is bound to a port supertest won't bind to an ephemeral port.

Here's an abridged version of how my app and test suite are written:

app.js

const express = require('express');
...
let app = express();
...
app.listen(process.env.PORT, function () {
    logger.info('App listening on port ' + process.env.PORT);
});

module.exports = app;

test.js

let app = require('../app.js'),
    agent = require('supertest').agent(app),
    assert = require('chai').assert;

describe('/api/auth/ typical sign up and login', function () {

    let email = 'foo@example.com',
        password = 'foobar',
        path = '/api';

    it('gets no results', async () => {
        let res = await agent
            .get(path + '/credentials/')
            .expect(200);

        assert.doesNotHaveAnyKeys(res.body, ['email']);
    });

    ...
}

For every await agent.(get|post|put)... there seems to be a connection on a different port. Is there some way to reduce the number of connections opened in my test suite? Is it possible to reuse a connection, or even just have connections close so they're not stuck in TIME_WAIT taking up resources?

Below is a sample output from netstat after a suite that made 11 "agent" calls - ports 6379 and 5432 are connections to redis and postgres respectively, all the rest I think are between supertest and my app:

tcp4       0      0  127.0.0.1.62021        127.0.0.1.6379         TIME_WAIT  
tcp4       0      0  127.0.0.1.62023        127.0.0.1.62024        TIME_WAIT  
tcp4       0      0  127.0.0.1.62026        127.0.0.1.62027        TIME_WAIT  
tcp4       0      0  127.0.0.1.62028        127.0.0.1.62029        TIME_WAIT  
tcp4       0      0  127.0.0.1.62030        127.0.0.1.62031        TIME_WAIT  
tcp4       0      0  127.0.0.1.62032        127.0.0.1.62033        TIME_WAIT  
tcp4       0      0  127.0.0.1.62034        127.0.0.1.62035        TIME_WAIT  
tcp4       0      0  127.0.0.1.62036        127.0.0.1.62037        TIME_WAIT  
tcp4       0      0  127.0.0.1.62038        127.0.0.1.62039        TIME_WAIT  
tcp4       0      0  127.0.0.1.62040        127.0.0.1.62041        TIME_WAIT  
tcp4       0      0  127.0.0.1.62042        127.0.0.1.62043        TIME_WAIT  
tcp4       0      0  127.0.0.1.62044        127.0.0.1.62045        TIME_WAIT  
tcp4       0      0  127.0.0.1.62022        127.0.0.1.6379         TIME_WAIT  
tcp4       0      0  127.0.0.1.62025        127.0.0.1.5432         TIME_WAIT  
mattdeluco commented 6 years ago

I figured out that passing a function to supertest.agent() will always cause supertest to bind to an ephemeral port on every request - the express app being a function.

Instead, I had to export the result of app.listen() (a NodeJS http.Server object) and pass that to supertest.agent():

app.js

...
let server = app.listen(process.env.PORT, function () {
    logger.info('App listening on port ' + process.env.PORT);
});

module.exports = server;

test.js

let server = require('../app.js'),
    agent = require('supertest').agent(server),
...

Now supertest opens a new socket for every request, but they all go to the same port on the server side.

Is it possible to reuse the sockets? Or have them close cleanly so they're not hanging around?

Zambonilli commented 6 years ago

I was able to set the http agent in superagent by wrapping the various HTTP verb method calls and then setting the agent. Combined with passing the http server returned from express app.listen this should give you a configurable number of sockets in a pool that make requests.

Here's the code I used to set the HTTP socket pool to 10 as well as reversing the cookie saving behavior of supertest when you set an agent.

` const http = require("http"); const keepAliveAgent = new http.Agent({ keepAlive: true, keepAliveMsecs: 90000, timeout: 1200000, maxSockets:10, maxFreeSockets:10 }); const _request = request.agent(app.app); _request.saveCookies = .noop;

['get', 'post', 'put', 'del', 'delete', 'head'].forEach((method)=>{ let fx = _request[method];

_request[method] = (...args)=>{
    let result = fx.call(_request, ...args);
    result.agent(keepAliveAgent);
    return result;
};

}); `

Lewiscowles1986 commented 2 years ago

Apparently this is solved.

https://stackoverflow.com/a/62278976/1548557

  1. Remove the app.listen call from the place you are currently passing to supertest.
  2. In {repo}/index.js you can .listen on the port you desire.

Alternatively, you can litter the code with test-specific logic as shown in another example https://stackoverflow.com/a/63293781/1548557 , but it just boils down to the above, which I think is cleaner.