'Leaking Mongoose connection in Jest tests

I am trying to setup supertest with jest and MongoDB InMemory Server, I've setup everything but I am unable to close my Mongoose connection. I believe that the problem lies in the fact that I create the connection from inside my app code and then try to close in on Teardown.

Here's the code to clarify

setupFile, this is getting called but the connection isn't closed

import mongoose from "mongoose";

afterAll(async () => {
    await mongoose.connection.close();
    // also tried mongoose.disconnect

});

Here's my global setup and teardown but these aren't too important I think

/* eslint-disable @typescript-eslint/no-explicit-any */
import config from "./utils/config";

import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
export = async function globalSetup() {
    if (config.Memory) {
        // Config to decided if an mongodb-memory-server instance should be used
        // it's needed in global space, because we don't want to create a new instance every test-suite
        const instance = await MongoMemoryServer.create();
        const uri = instance.getUri();
        (global as any).__MONGOINSTANCE = instance;
        process.env.MONGODB_URI = uri.slice(0, uri.lastIndexOf("/"));
    } else {
        process.env.MONGODB_URI = `mongodb://${config.IP}:${config.Port}`;
    }

    // The following is to make sure the database is clean before an test starts
    await mongoose.connect(`${process.env.MONGODB_URI}/${config.Database}`, {});
    await mongoose.connection.db.dropDatabase();
    await mongoose.disconnect();
};

Global teardown

import config from "./utils/config";

import { MongoMemoryServer } from "mongodb-memory-server";
export = async function globalTeardown() {
    if (config.Memory) {
        // Config to decided if an mongodb-memory-server instance should be used
        const instance: MongoMemoryServer = (global as any).__MONGOINSTANCE;
        await instance.stop();
    }
};

Now he's the real meat of the problem

example.test.ts

import { app } from "@/server"; HERE I AM IMPORTING MY APP

    it("it should have status code 200 and create an account", async function () {
        //GIVEN
       ...

        //WHEN
        await request(app)
            .post("/api/account/")
            .send(somedata)
            .expect(200);

        //SHOULD
    });

And my app code

async function closeGracefully(signal) {
    console.log(`*^!@4=> Received signal to terminate: ${signal}`);

    await server.close();
    await mongoose.connection.close();
    // await other things we should cleanup nicely
    process.exit();
}
process.on("SIGINT", closeGracefully);
process.on("SIGTERM", closeGracefully);

let app;

async function startServer() {
    app = express();
    await require("./loaders").default({ expressApp: app });
    server = app
        .listen(...)
}

startServer();

export { app };

The start server calls the loaders and one of the loaders is

// THIS IS WHAT I AM LEAKING
export default async (): Promise<void> => {
    LoggerInstance.info("Connecting to database at ", config.databaseUrl);
    await mongoose.connect(config.databaseUrl);
    return;
};

And so we came a full circle, the test getting the app calls the mongoose.connect and for some reason, calling disconnect or stop from afterAll or globalTeardown doesn't close the connection. My suspicion is that somehow the mongoose is a different instance or something but mongoose.connections.length always says 1 even after I close it. I am super confused as to what is happening. Besides this, it's working properly, just the tests are hanging as the connection is open.

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. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.


Solution 1:[1]

Managed to resolve this by ditching the /src express app and just creating a new Express app inside my tests that I export and call the same loaders on, I also added a check whether or not we're in test environment, and if we are don't connect to the DB from the loaders but rather I'll manually connect, this is the final code

// src/loaders

export default async ({ expressApp }) => {
    config.environment != "test" && await mongooseLoader();
    Logger.info("DB loaded and connected!");

And my setup file

import loaders from "@/loaders";

import express from "express";
import mongoose from "mongoose";

const app = express();

beforeAll(async () => {
    await mongoose.connect(process.env.MONGODB_URI + "/test-db");
    await loaders({ expressApp: app });
});

afterAll(async () => {
    await mongoose.connection.close();
});

export default app;

Probably there is a more elegant solution so I'll keep this open, but this doesn't leak and seems to work as intended

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nikola-Milovic