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

如何为NestJS单仓储模块正确Mock MongoDB进行单元测试

问题解决:NestJS通用MongoRepository的单元测试方案

一、当前测试的依赖缺失问题修复

你的测试代码无法解析依赖,核心是未提供MongoRepository和forwardRef引入的ChannelsModule相关依赖。下面分两种主流方案解决:

方案1:Mock MongoRepository(纯单元测试,隔离数据库)

单元测试的核心是验证业务逻辑而非数据库交互,直接Mock通用仓储的方法即可,无需启动真实数据库:

// 先定义测试用的模拟数据
const mockConversation = { _id: 'conv-1', title: 'Test Conversation' };

beforeAll(async () => {
  const app: TestingModule = await Test.createTestingModule({
    providers: [
      ConversationsService, // 替换你之前误写的AppService
      // Mock Conversation Model(如果服务直接注入Model的话)
      {
        provide: getModelToken(Conversation.name),
        useValue: jest.fn(),
      },
      // Mock通用MongoRepository的所有调用方法
      {
        provide: MongoRepository,
        useValue: {
          findOne: jest.fn().mockResolvedValue(mockConversation),
          findOneById: jest.fn().mockResolvedValue(mockConversation),
          updateOneById: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
          // 按需添加服务中用到的其他仓储方法
        },
      },
      // 处理forwardRef的ChannelsModule:Mock其导出的核心服务
      {
        provide: ChannelsService,
        useValue: {
          getChannelById: jest.fn().mockResolvedValue({ _id: 'chan-1' }),
          // 按需添加服务依赖的ChannelsService方法
        },
      },
    ],
  }).compile();

  conversationService = app.get<ConversationsService>(ConversationsService);
});

方案2:内存MongoDB集成测试(验证仓储真实逻辑)

如果需要测试仓储与数据库的交互逻辑,用MongoMemoryServer搭建临时数据库,同时正确配置Nest模块:

let mongod: MongoMemoryServer;
let mongoConnection: Connection;

beforeAll(async () => {
  mongod = await MongoMemoryServer.create();
  const uri = mongod.getUri();

  const app: TestingModule = await Test.createTestingModule({
    imports: [
      // 配置Mongoose根连接,指向内存Mongo
      MongooseModule.forRoot(uri),
      // 注册Conversation实体的Model
      MongooseModule.forFeature([{ name: 'Conversation', schema: ConversationSchema }]),
      // 导入通用MongoRepository模块
      MongoRepository,
      // 处理forwardRef的ChannelsModule:要么导入其测试模块,要么Mock
      forwardRef(() => ({
        module: class MockChannelsModule {},
        providers: [{ provide: ChannelsService, useValue: {} }],
        exports: [ChannelsService],
      })),
    ],
    providers: [ConversationsService],
  }).compile();

  conversationService = app.get<ConversationsService>(ConversationsService);
  mongoConnection = app.get<Connection>(Connection);
});

// 清理测试数据
afterAll(async () => {
  await mongoConnection.dropDatabase();
  await mongoConnection.close();
  await mongod.stop();
});

afterEach(async () => {
  const collections = mongoConnection.collections;
  for (const key in collections) {
    await collections[key].deleteMany({});
  }
});

二、通用仓储(非单实体对应单仓储)的标准测试方式

1. 仓储自身的测试(集成测试)

针对MongoRepository的方法逻辑(如查询、更新正确性),用内存Mongo做集成测试,验证与数据库的真实交互:

describe('MongoRepository', () => {
  let repository: MongoRepository;
  let testModel: Model<TestEntity>;
  let mongod: MongoMemoryServer;
  let connection: Connection;

  beforeAll(async () => {
    mongod = await MongoMemoryServer.create();
    const uri = mongod.getUri();
    connection = (await mongoose.connect(uri)).connection;
    testModel = connection.model('TestEntity', TestEntitySchema);
    repository = new MongoRepository(); // 或通过Nest容器获取实例
  });

  afterAll(async () => {
    await connection.dropDatabase();
    await connection.close();
    await mongod.stop();
  });

  it('should return correct document when findOne is called', async () => {
    const testDoc = await testModel.create({ name: 'test' });
    const result = await repository.findOne(testModel, { name: 'test' });
    
    expect(result).toMatchObject({ name: 'test', _id: testDoc._id });
  });
});

2. 依赖仓储的服务测试(单元测试)

针对业务服务(如ConversationsService),专注验证业务逻辑,Mock仓储的所有调用:

describe('ConversationsService', () => {
  let service: ConversationsService;
  let mockRepo: jest.Mocked<MongoRepository>;

  beforeEach(async () => {
    // 初始化Mock仓储
    mockRepo = {
      findOneById: jest.fn(),
      updateOneById: jest.fn(),
    } as jest.Mocked<MongoRepository>;

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ConversationsService,
        { provide: MongoRepository, useValue: mockRepo },
        { provide: ChannelsService, useValue: {} }, // Mock其他依赖
      ],
    }).compile();

    service = module.get<ConversationsService>(ConversationsService);
  });

  it('should call repository findOneById when getting conversation', async () => {
    const mockResult = { _id: '123', title: 'Test' };
    mockRepo.findOneById.mockResolvedValue(mockResult);

    const result = await service.getConversation('123');
    
    expect(mockRepo.findOneById).toHaveBeenCalledWith(expect.any(Model), '123');
    expect(result).toEqual(mockResult);
  });
});

三、常见问题排查

  • 依赖遗漏:检查服务注入的所有提供者(仓储、其他服务、Model)是否都在测试模块中提供了Mock或真实实例
  • 循环依赖处理:对于forwardRef引入的模块,要么Mock其导出的核心服务,要么用overrideModule替换为测试专用模块
  • 类型安全:用jest.Mocked<T>包裹Mock对象,确保与原类的方法签名匹配

内容的提问来源于stack exchange,提问作者sant016

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 04:05:48