如何在NestJS中配置MongoDB内存服务器用于E2E测试?
问题分析
- 测试生命周期错误:
afterEach中调用closeInMongodConnection会断开Mongoose连接并停止内存MongoDB服务器,但beforeAll仅初始化一次应用和模块,后续测试会因无数据库连接返回500错误。 - 配置冲突:
AppModule可能已导入MongooseModule.forRoot指向外部数据库,测试时同时导入AppModule和rootMongooseTestModule会导致配置冲突,内存数据库未实际生效。
解决方案
1. 重构Mongoose测试模块,拆分资源管理逻辑
修改MongooseTestModule.ts,将数据清空与资源销毁分离,保留内存服务器在测试全程运行:
import { MongooseModule, MongooseModuleOptions } from '@nestjs/mongoose'; import { MongoMemoryServer } from 'mongodb-memory-server'; import { disconnect, connection } from 'mongoose'; let mongod: MongoMemoryServer; export const rootMongooseTestModule = (options: MongooseModuleOptions = {}) => MongooseModule.forRootAsync({ useFactory: async () => { mongod = await MongoMemoryServer.create(); const mongoUri = mongod.getUri(); return { uri: mongoUri, ...options, }; }, }); // 仅清空所有集合数据,保留连接和服务器运行 export const clearDatabase = async () => { const collections = connection.collections; for (const key in collections) { await collections[key].deleteMany({}); } }; // 所有测试结束后销毁资源 export const closeInMongodConnection = async () => { await disconnect(); if (mongod) await mongod.stop(); };
2. 调整测试文件的生命周期钩子
修改drive.e2e-spec.ts,将资源销毁移至afterAll,afterEach仅清空数据,同时确保内存数据库配置覆盖AppModule的默认配置:
import { HttpStatus, INestApplication } from '@nestjs/common'; import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; import { Model } from 'mongoose'; import * as request from 'supertest'; import { AppModule } from '#app.module'; import { CreateDriveDto } from '#drive/dto/create-drive.dto'; import { Drive, DriveDoc } from '#drive/schema/drive.schema'; import { closeInMongodConnection, rootMongooseTestModule, clearDatabase, } from 'test/util/MongooseTestModule'; import { dataFromJoi } from 'test/util/generate-valid-sample-data'; import { configTestApp } from 'test/util/setup-test-app'; const route = `/api/test/v2/drive`; const validCreateData = dataFromJoi(CreateDriveDto); let moduleFixture: TestingModule; let app: INestApplication; let mockDriveModel: Model<DriveDoc>; beforeAll(async () => { moduleFixture = await Test.createTestingModule({ imports: [ // 先导入内存数据库模块,覆盖AppModule的Mongoose配置 rootMongooseTestModule(), AppModule, ], }).compile(); app = moduleFixture.createNestApplication(); mockDriveModel = moduleFixture.get<Model<DriveDoc>>(getModelToken(Drive.name)); configTestApp(app); await app.init(); }); // 每个测试后清空数据,避免测试间状态污染 afterEach(async () => { await clearDatabase(); }); // 所有测试完成后销毁资源 afterAll(async () => { await closeInMongodConnection(); await app.close(); }); describe(`Integration Test :: Route ${route}`, () => { describe(`POST`, () => { test('Return 400 if request is invalid', async () => { await request(app.getHttpServer()) .post(route) .send({}) .expect(HttpStatus.BAD_REQUEST) .then((res) => { expect(res.body).toHaveProperty('statusCode'); expect(res.body).toHaveProperty('message'); }); }); test('Return 201 and create new entry if request is valid', async () => { await request(app.getHttpServer()) .post(route) .send(validCreateData) .expect(HttpStatus.CREATED); }); }); describe(`GET`, () => { test('Return 200 and an array', async () => { await mockDriveModel.create(validCreateData); await request(app.getHttpServer()) .get(route) .expect(HttpStatus.OK) .then((res) => { expect(Array.isArray(res.body)).toBeTruthy(); }); }); }); });
3. 处理硬编码配置的情况
如果AppModule中直接硬编码了Mongoose连接地址,需要通过overrideModule强制替换配置:
moduleFixture = await Test.createTestingModule({ imports: [AppModule], }) .overrideModule(MongooseModule) .useModule(rootMongooseTestModule()) .compile();
其他测试策略建议
- 模型模拟:单元测试中可使用
jest.mock模拟Mongoose模型,跳过数据库操作; - 独立套件初始化:为每个测试套件单独创建应用和数据库实例,彻底隔离测试状态(会增加测试耗时);
- 事务回滚:MongoDB 4.0+支持事务,可在测试后回滚操作替代清空集合,提升测试效率。
内容的提问来源于stack exchange,提问作者Nathan Bernardo
相关产品推荐
相关产品推荐

