Dealing with Unexpected Timeout Errors and Memory Leaks in Express/Typescript Using Jest, Supertest, and TypeORM

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.

Answer №1

Resolution Update

After thorough investigation, it appears that the two issues mentioned are not correlated. Fortunately, I have identified a workaround for the timeout error problem.

The root cause seems to stem from how the database connection was initiated using typeorm.

To address this, I have segregated the database connection logic from the express server and initialized it separately within a dedicated setupFilesAfterEnv file. Within the beforeAll function, I check if there is an existing connection. If not, I establish a new connection; otherwise, I utilize the .connect() method on the current connection.

Currently, my testing script has been running flawlessly for over an hour without encountering any errors, indicating that the problem has been successfully resolved.

Although, I am still observing the MaxListenersExceededWarning alert.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue: The use of destructuring props is required by eslint, and I'm currently working with a combination of react and types

I typically work with React in JavaScript, and I recently encountered an error message stating "Must use destructuring props at line 14" while trying to define a button for use in a form. Here is the code snippet in question: import React from 'react& ...

Discover the combined type of values from a const enum in Typescript

Within my project, some forms are specified by the backend as a JSON object and then processed in a module of the application. The field type is determined by a specific attribute (fieldType) included for each field; all other options vary based on this ty ...

What is the process for building .ts components within a Vue application?

Looking to update an old project that currently uses the 'av-ts' plugin and vue-ts-loader to compile .ts files as Vue components. The project is running on Vue 2.4.2, but I want to upgrade it to version 2.6.14. However, since 'vue-ts-loader& ...

Refreshing the page causes the Angular/Ionic Singleton instance to be destroyed

I have a TypeScript singleton class that is responsible for storing the login credentials of a user. When I set these credentials on the login page and navigate to the next page using Angular Router.navigate (without passing any parameters), everything wor ...

Modify information in the database using Mongoose

Attempting to update data in my database using Mongoose and NodeJS, I am able to retrieve data from the logged in user, but encountering issues with updating it. I have a form on the front-end that fetches data from the logged in user (views/pages/profile- ...

Navigating through Subroutes while utilizing Middleware in NodeJs Express

I have decided not to include all my main routes in the server.js file of my express server because there are too many. Instead, I have created a subroute called auth where login, register, email verification, and password reset functionalities reside. Tw ...

typescript set x and y values to specific coordinates

Trying to map obstacles using a single object. Originally scattered randomly across the map, now I want to hard code X & Y coordinates with an array of numbers. However, TypeScript is only using the last value of the loop for the X coordinate. How can I a ...

A current issue lies in the fact that node.js is failing to update

I'm currently working on creating a functionality that involves reading a CSV file and checking if the headers are correct before proceeding. Below is the code snippet: let validHeaders = false; fs.createReadStream(path) .pipe(csv.parse({headers : ...

Using Magento's Paypal Express feature, customers can easily checkout using their Paypal

Running my E-commerce store on Magento, I encountered an issue with Paypal Express checkout. After selecting Paypal as the payment option and logging in on the Paypal platform to confirm the payment, I was redirected back to my Magento shop's review p ...

Strange behavior of Backbone.js save

After creating a new model (msg), I proceed to save it using the following code snippet: msg.save({}, {success: this.createSuccess, error: function(model, response){ console.log('nay', response); }}); Despite receiving a status: 200 and statu ...

Unlocking new perspectives with a click

Currently exploring Angular development, I have encountered a question here but couldn't find the solution I was looking for. I am seeking suggestions and ideas on how to approach this issue. Essentially, my HTML includes buttons like the ones shown ...

Conflicts Between AngularJS and ExpressJS Routing

For a specific array object, I am attempting to pass an index value into a route. This index will be used to load that object into a detail view. Here is the controller code that injects the index and changes the path: location.path('/detail/'+ ...

The 'toDataURL' property is not recognized on the 'HTMLElement' type

Hey there! I'm new to TypeScript and I've been experimenting with generating barcodes in a canvas using JSBarcode and then adding it to JSpdf as an image using the addImage function. However, I keep encountering the error mentioned above. barcod ...

Understanding NestJS Mixins and Their Distinction from Inheritance

After researching, I found that the Nestjs documentation does not include any information about mixins. Here is what I have gathered from my findings on Google and Stack Overflow: A mixin serves as a means of code sharing between classes within Nest. Esse ...

Achieving efficient handling of React builds in NodeJS/Express

Can someone provide guidance on effectively managing React builds in an Express app? I am currently facing a major issue where the React app always takes precedence over other endpoints when accessed through a browser, even though it is located at the end ...

Is there a way to utilize const assertions to retrieve the explicit types from objects nested at various levels?

In reference to this question, the previous structure had a depth of 2: const grandkids = { Karen: { Ava: ['Alice', 'Amelia'], Emma: ['Sarah'], }, Mary: { Sophia: ['Grace'], }, } as const; To ext ...

Running TypeScript Jest tests in Eclipse 2020-12: A step-by-step guide

Having a TypeScript project with a test suite that runs smoothly using npm test in the project directory from the console. Is there a feature within Eclipse that can facilitate running these tests directly within the IDE, similar to how Java tests are ex ...

Avoid generating file modifications due to a version update when using OpenApiGenerator

When utilizing the typescript-rxjs generator, the issue arises when generating a new version of API clients. The majority of files are altered due to a simple version change: * The version of the OpenAPI document: 1.47.0-rc.20. This results in real change ...

Can we programmatically switch to a different mat-tab that is currently active?

Looking to programmatically change the selected mat-tab in a TypeScript file. I came across a solution for md-tab, but I need it for mat-tab. I attempted the suggested solution without success. Here's what I tried: HTML <button class="btn btn- ...

Determine the size of the resulting zip file with NodeJs

I am currently utilizing NodeJs (expressJs) along with the archiver module to stream a zip file directly to the client without storing the final ZIP file on the server (streaming zip serving). However, one issue I am facing is that I am unable to retrieve ...