You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Jest与MongoMemoryServer测试遇UnexpectedCloseError(code48)求助

Jest + mongo-memory-server 测试报错排查

运行全部测试时触发 UnexpectedCloseError: Instance closed unexpectedly with code "48" and signal "null" 错误,但单独执行失败的测试用例时全部通过。

错误信息

PASS  src/test/health.test.ts
 FAIL  src/test/user.test.ts
  ● Console

    console.warn
      Starting the MongoMemoryServer Instance failed, enable debug log for more information. Error:
       UnexpectedCloseError: Instance closed unexpectedly with code "48" and signal "null"
          at MongoInstance.closeHandler (/Users/ertan/Desktop/jsarticles/jsad-backend/node_modules/mongodb-memory-server-core/src/util/MongoInstance.ts:546:52)
          at ChildProcess.emit (node:events:520:28)
          at maybeClose (node:internal/child_process:1092:16)
          at Process.ChildProcess._handle.onexit (node:internal/child_process:302:5)

      at MongoMemoryServer.<anonymous> (node_modules/mongodb-memory-server-core/src/MongoMemoryServer.ts:293:17)
      at node_modules/mongodb-memory-server-core/node_modules/tslib/tslib.js:169:75
      at __awaiter (node_modules/mongodb-memory-server-core/node_modules/tslib/tslib.js:165:16)
      at node_modules/mongodb-memory-server-core/src/MongoMemoryServer.ts:284:68

  ● User Models › should insert a doc into user collection

    Instance closed unexpectedly with code "48" and signal "null"

      at MongoInstance.closeHandler (node_modules/mongodb-memory-server-core/src/util/MongoInstance.ts:546:52)

  ● User Models › should find all users

    Instance closed unexpectedly with code "48" and signal "null"

      at MongoInstance.closeHandler (node_modules/mongodb-memory-server-core/src/util/MongoInstance.ts:546:52)

  ● User Models › should find user by email

    Instance closed unexpectedly with code "48" and signal "null"

测试代码(src/test/employee.test.ts)

import {
  afterAll,
  afterEach,
  beforeAll,
  describe,
  it,
  jest,
  expect,
} from "@jest/globals";

import {
  connectDB,
  dropCollections,
  dropDB,
} from "../configs/mongoMemoryServer";

import {
  UserModel,
  createUser,
  deleteUserById,
  findUserByEmail,
  findUserById,
  findUsers,
} from "../model/user";

jest.useRealTimers();

describe("User Models", () => {
  beforeAll(async () => {
    await connectDB();
  });

  afterAll(async () => {
    await dropDB();
  });

  afterEach(async () => {
    await dropCollections();
  });

  it("should insert a doc into user collection", async () => {
    const MOCK_USER = {
      email: "test@yopmail.com",
      gdprInfo: {
        isAccepted: true,
        ip: "111.111.1.1",
      },
    };

    const createdUser = await createUser(MOCK_USER.email, MOCK_USER.gdprInfo);

    expect(createdUser.email).toBe(MOCK_USER.email);
    expect(createdUser.gdprInfo.ip).toBe(MOCK_USER.gdprInfo.ip);
    expect(createdUser.gdprInfo.isAccepted).toBe(MOCK_USER.gdprInfo.isAccepted);
    expect(createdUser.sentNewsletters).toEqual([]);
    expect(createdUser.createdAt).toBeInstanceOf(Date);
    expect(createdUser.updatedAt).toBeInstanceOf(Date);
  });

  it("should find all users", async () => {
    const MOCK_USERS = [
      {
        email: "test@outlook.com",
        gdprInfo: {
          isAccepted: true,
          ip: "111.111.1.1",
        },
      },
      {
        email: "test@gmail.com",
        gdprInfo: {
          isAccepted: true,
          ip: "111.111.1.2",
        },
      },
    ];

    await createUser(MOCK_USERS[0].email, MOCK_USERS[0].gdprInfo);
    await createUser(MOCK_USERS[1].email, MOCK_USERS[1].gdprInfo);

    const users = await findUsers([]);

    expect(users).toBeDefined();
    expect(users.length).toBeGreaterThan(0);

    users.forEach((user) => {
      expect(user).toBeInstanceOf(UserModel);
    });
  });

  it("should find user by email", async () => {
    const MOCK_USERS = [
      {
        email: "test@outlook.com",
        gdprInfo: {
          isAccepted: true,
          ip: "111.111.1.1",
        },
      },
      {
        email: "test@gmail.com",
        gdprInfo: {
          isAccepted: true,
          ip: "111.111.1.2",
        },
      },
    ];

    await createUser(MOCK_USERS[0].email, MOCK_USERS[0].gdprInfo);
    await createUser(MOCK_USERS[1].email, MOCK_USERS[1].gdprInfo);

    const user = await findUserByEmail("test@outlook.com");

    expect(user).toBeDefined();
    expect(user).toBeInstanceOf(UserModel);
    expect(user?.email).toBe("test@outlook.com");
    expect(user?.email).not.toBe("test@gmail.com");
  });

  it("should find user by id", async () => {
    const MOCK_USER = {
      email: "test@outlook.com",
      gdprInfo: {
        isAccepted: true,
        ip: "111.111.1.1",
      },
    };

    const createdUser = await createUser(MOCK_USER.email, MOCK_USER.gdprInfo);

    const user = await findUserById(createdUser?._id?.toString() || "");

    expect(user).toBeDefined();
    expect(user).toBeInstanceOf(UserModel);
    expect(user?.email).toBe("test@outlook.com");
  });

  it("should delete user", async () => {
    const MOCK_USER = {
      email: "test@outlook.com",
      gdprInfo: {
        isAccepted: true,
        ip: "111.111.1.1",
      },
    };

    const createdUser = await createUser(MOCK_USER.email, MOCK_USER.gdprInfo);

    const deletedUser = await deleteUserById(
      createdUser?._id?.toString() || ""
    );

    expect(deletedUser).toBeDefined();
    expect(deletedUser).toBeInstanceOf(UserModel);
    expect(deletedUser?._id).toEqual(createdUser._id);
  });
});

数据库配置代码(configs/mongoMemoryServer)

import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose, { ConnectOptions } from "mongoose";

let mongo: MongoMemoryServer;

const connectDB = async () => {
  mongo = await MongoMemoryServer.create({
    instance: {
      port: Number(process.env.DB_PORT),
    },
  });
  const uri = mongo.getUri();

  await mongoose.connect(uri, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  } as ConnectOptions);
};

const dropDB = async () => {
  if (mongo) {
    await mongoose.connection.dropDatabase();
    await mongoose.connection.close();
    await mongo.stop();
  }
};

const dropCollections = async () => {
  if (mongo) {
    const collections = await mongoose.connection.db.collections();
    for (const collection of collections) {
      await collection.drop();
    }
  }
};

export { connectDB, dropDB, dropCollections };

版本信息

  • NodeJS: v16.14.0
  • mongodb-memory-server-*: ^8.13.0
  • mongoose: ^7.2.2
  • Jest: ^29.5.0
  • 系统: macOS M1

解决方案

1. 端口冲突问题

错误码48通常表示端口被占用。你在connectDB中指定了固定端口process.env.DB_PORT,当并行运行多个测试文件时,第一个测试启动的Mongo实例占用了该端口,第二个测试尝试启动时就会失败。

修复方式:移除固定端口配置,让mongo-memory-server自动分配可用端口:

// configs/mongoMemoryServer.js
const connectDB = async () => {
  // 去掉instance.port配置
  mongo = await MongoMemoryServer.create();
  const uri = mongo.getUri();

  await mongoose.connect(uri, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  } as ConnectOptions);
};

2. Jest并行执行导致的资源竞争

Jest默认并行运行测试文件,多个测试文件同时操作Mongo实例会导致资源冲突。可以通过以下方式解决:

方式一:禁用Jest并行执行

在jest.config.js中添加:

module.exports = {
  // ...其他配置
  maxWorkers: 1,
};

方式二:使用全局测试钩子共享Mongo实例

创建全局的测试初始化文件(如src/test/setup.ts),在所有测试文件之前启动一次Mongo实例,测试结束后关闭:

// src/test/setup.ts
import { connectDB, dropDB, dropCollections } from "../configs/mongoMemoryServer";

beforeAll(async () => {
  await connectDB();
});

afterAll(async () => {
  await dropDB();
});

afterEach(async () => {
  await dropCollections();
});

然后在jest.config.js中配置:

module.exports = {
  // ...其他配置
  setupFilesAfterEnv: ["<rootDir>/src/test/setup.ts"],
};

同时删除每个测试文件中的beforeAll、afterAll、afterEach钩子。

3. 清理资源不彻底

确保dropDB和dropCollections中的异步操作正确执行,避免残留资源导致后续测试失败。可以在dropDB中增加错误捕获:

const dropDB = async () => {
  if (mongo) {
    try {
      await mongoose.connection.dropDatabase();
      await mongoose.connection.close();
      await mongo.stop();
    } catch (err) {
      console.error("清理数据库失败:", err);
    }
  }
};

内容的提问来源于stack exchange,提问作者Ertan Özdemir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 03:34:53