Currently, I am in the process of writing unit tests for an express API. Each test suite initiates a fresh instance of an express server before running the tests.
Individually, each test suite runs smoothly without any problems. However, when executed together, some issues start to arise.
Upon completion of 9 test suites, every individual test begins displaying a warning message that reads:
(node:5282) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 connection listeners added to [Server]. Use emitter.setMaxListeners() to increase limit
at _addListener (node:events:453:17)
at Server.addListener (node:events:469:10)
at Server.<anonymous> (node_modules/async-listener/index.js:90:10) => this.on('connection', function (socket) {...
at Server.<anonymous> (node_modules/async-listener/index.js:97:23) => return original.apply(this, arguments);
at Server.<anonymous> (node_modules/async-listener/index.js:97:23)
at Server.<anonymous> (node_modules/async-listener/index.js:97:23)
at Server.<anonymous> (node_modules/async-listener/index.js:97:23)
at Server.<anonymous> (node_modules/async-listener/index.js:97:23)
at Server.<anonymous> (node_modules/async-listener/index.js:97:23)
at Server.<anonymous> (node_modules/async-listener/index.js:97:23)
I don't want to simply ignore or suppress this warning, especially if it indicates a potential memory leak, so please avoid suggesting that.
Occasionally, certain tests will fail with the error message:
thrown: "Exceeded timeout of 20000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
I am confident that this issue is not related to the specified timeout value because these failing tests complete in under 500ms, regardless of how the timeout value is adjusted.
Sample .test.ts File
import Server from '../../server';
import { generateTestData } from '../generate-database-data';
import * as request from 'supertest';
import { APIRoutes } from '../../routes';
const server = new Server(); // New instance of express server
describe('API Test', () => {
// Sets up TypeORM DB connection
beforeAll(async() => await server.connect());
// Run raw sql statements using typeorm query runner to truncate and populate fresh data for each test case
beforeEach(async() => await generateTestData(server.connection));
// Close TypeORM connection after all tests
afterAll(async () => await server.connection.close());
test('Get All', async () => {
const response = await request(server.app)
.get(APIRoutes.ROUTE)
.set('Accept', 'application/json');
expect(response.status).toEqual(200);
expect(response.body.length).toEqual(4);
});
...
});
Server.ts
import * as express from 'express';
import 'express-async-errors';
import { createConnection } from 'typeorm';
import { configureRoutes } from './routes';
require('dotenv').config({path: process.env.NODE_ENV === 'test' ? '.env.test' : '.env'});
class Server {
public app: express.Application;
public server;
public connection;
constructor() {
this.app = express();
this.configuration();
}
public configuration() {
this.app.set('port', process.env.PORT);
this.app.use(express.json());
}
public async connect() {
this.connection = await createConnection({
'type': 'mysql',
'host': process.env.DB_HOST,
'port': parseInt(process.env.DB_PORT),
'username': process.env.DB_USERNAME,
'password': process.env.DB_PASSWORD,
'database': process.env.DB_DATABASE,
'dropSchema': process.env.DB_DROP_SCHEMA === 'true',
'synchronize': true,
'logging': ['error'],
'logger': 'file',
'entities': process.env.NODE_ENV === 'test' ? ['src/entities/**/*.ts'] : ['build/src/entities/**/*.js'],
'cli': { 'entitiesDir': 'build/src/entities' },
'extra': process.env.NODE_ENV === 'test' ? {
'connectionLimit': 0
} : {},
});
configureRoutes(this.app);
return
}
public async start() {
await this.connect();
if (process.env.NODE_ENV !== 'test') {
this.server = this.app.listen(process.env.PORT, () => {
console.log(`App listening on the port ${process.env.PORT}`);
});
}
}
public async stop() {
await this.server?.close();
await this.connection?.close();
}
}
// start express server
if(process.env.NODE_ENV !== 'test') {
const server = new Server();
server.start();
}
export default Server;
Related Dependencies
"devDependencies": {
"@babel/core": "^7.17.8",
"@babel/preset-env": "^7.16.11",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.1",
"@types/node": "^17.0.18",
"@types/supertest": "^2.0.12",
"babel-jest": "^27.5.1",
"jest": "^27.2.0",
"jest-express": "^1.12.0",
"supertest": "^6.1.6",
"ts-jest": "^27.0.5",
"ts-node": "^10.5.0",
"typescript": "^4.5.5"
},
"dependencies": {
"dotenv": "^8.6.0",
"express": "^4.17.2",
"express-async-errors": "^3.1.1",
"helmet": "^5.0.2",
"typeorm": "^0.2.42"
}
I strongly suspect that these issues are not directly connected to typeorm and the database connection. Even after removing the connection and related queries generating test data, certain tests still fail with the same timeout error and warning.
Considering the warning message
at Server.addListener (node:events:469:10)
, I have reason to believe that this error might be linked to either the express server itself or supertest.