使用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
相关产品推荐
相关产品推荐

