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

如何在NestJS中对依赖两个模型的功能进行单元测试?

为依赖其他MongoDB仓库的NestJS仓库编写单元测试

问题场景

你有两个NestJS Mongoose仓库:UsersRepository依赖OrdersRepository,调用其find方法实现getOrders功能。编写单元测试时,添加第二个仓库/模型后出现connection.close()为undefined的错误。

相关代码

UsersRepository与UserModule

@Injectable()
export class UsersRepository extends EntityRepository<UserDocument> {
    constructor(
      @InjectModel(User.name) userModel: Model<UserDocument>,
      protected readonly orderRepository: OrdersRepository  
     ) {
      super(userModel)
    }
    
    async getOrders(id: string) {
      const orders = await this.orderRepository.find({ userId: id });
      return orders;
    }
}

@Module({
  imports: [
    MongooseModule.forFeature(
      [{ name: User.name, schema: UserSchema }],
      'TEST-V2',
    ),
    OrderModule,
  ],
  controllers: [UserController],
  providers: [UserService, UserRepository],
  exports: [UserService, UserRepository],
})
export class UserModule {}

OrdersRepository与OrderModule

@Injectable()
export class OrdersRepository extends EntityRepository<OrderDocument> {
    constructor(@InjectModel(Order.name) orderModel: Model<OrderDocument>) {
      super(orderModel)
    }
}

@Module({
  imports: [
    MongooseModule.forFeature(
      [{ name: Order.name, schema: OrderSchema }],
      'TEST-V2',
    ),
  ],
  providers: [OrderService, OrderRepository],
  exports: [OrderService, OrderRepository],
})
export class OrderModule {}

你的测试代码(存在问题)

describe('UsersRepository', () => {
  let userModel: UserModel;
  let userRepository: UserRepository;
  let orderRepository: OrdersRepository;
  let orderModel: OrderModel;
  let connection: Connection;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
        imports: [
          ConfigModule.forRoot({
            validationSchema: envVarValidationSchema,
            isGlobal: true,
            cache: true,
            envFilePath: `.env`,
            validationOptions: {
              allowUnknown: true,
              abortEarly: false,
            },
            load: [configuration],
          }),
          MongooseModule.forRootAsync({
            connectionName: 'TEST-V2',
            imports: [ConfigModule],
            inject: [ConfigService],
            useFactory: async (configService: ConfigService) => ({
              uri: configService.get('db.mongo.testv2'),
            }),
          }),
        ],
        providers: [
          UsersRepository,
          OrdersRepository,
          {
            provide: getModelToken(Order.name),
            useClass: OrderModel,
          },
          {
            provide: getModelToken(User.name),
            useClass: UserModel,
          },
        ],
      }).compile();
    // ... 获取实例
    connection = await moduleRef.get(getConnectionToken('TEST-V2'));
    jest.clearAllMocks();
  })

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

错误原因

  1. 单元测试思路错误:你当前写法偏向集成测试,但手动用useClass绑定模型,导致Mongoose连接未正确关联到模型实例,最终connection获取失败为undefined。
  2. 依赖注入混乱:手动注册仓库和模型的方式,破坏了NestJS模块的自动依赖解析逻辑,导致连接实例无法正常注入。

解决方案

方案一:模拟OrdersRepository(单元测试推荐)

单元测试核心是隔离依赖,无需连接真实数据库,直接模拟OrdersRepository的方法即可:

describe('UsersRepository', () => {
  let userRepository: UsersRepository;
  // 模拟OrdersRepository实例
  let mockOrderRepository: Partial<OrdersRepository>;

  beforeEach(async () => {
    // 创建模拟对象,预设find方法返回值
    mockOrderRepository = {
      find: jest.fn().mockResolvedValue([{ _id: 'order1', userId: 'user1' }])
    };

    const moduleRef = await Test.createTestingModule({
      providers: [
        UsersRepository,
        // 注入模拟的OrdersRepository
        { provide: OrdersRepository, useValue: mockOrderRepository },
        // 模拟UserModel(测试getOrders无需实际操作User模型)
        {
          provide: getModelToken(User.name),
          useValue: jest.fn()
        }
      ]
    }).compile();

    userRepository = moduleRef.get(UsersRepository);
  });

  it('should return orders via OrderRepository', async () => {
    const userId = 'user1';
    const result = await userRepository.getOrders(userId);

    // 验证mock方法被正确调用
    expect(mockOrderRepository.find).toHaveBeenCalledWith({ userId });
    // 验证返回值符合预期
    expect(result).toEqual([{ _id: 'order1', userId: 'user1' }]);
  });
});

方案二:使用测试数据库(集成测试)

如果需要测试真实数据库交互,直接导入模块而非手动注册模型,让NestJS自动处理连接:

describe('UsersRepository (Integration)', () => {
  let userRepository: UsersRepository;
  let connection: Connection;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          envFilePath: '.env.test' // 使用单独的测试环境变量文件
        }),
        // 直接导入UserModule,自动关联OrderModule和Mongoose连接
        UserModule
      ]
    }).compile();

    userRepository = moduleRef.get(UsersRepository);
    connection = moduleRef.get(getConnectionToken('TEST-V2'));
  });

  afterEach(async () => {
    // 清空测试数据,避免影响后续测试
    await connection.collection('users').deleteMany({});
    await connection.collection('orders').deleteMany({});
    await connection.close();
  });

  it('should fetch real orders for a user', async () => {
    // 插入测试数据
    await connection.collection('orders').insertOne({ userId: 'user2', product: 'test-product' });
    
    const result = await userRepository.getOrders('user2');
    
    expect(result.length).toBe(1);
    expect(result[0].userId).toBe('user2');
  });
});

关键注意点

  • 单元测试:聚焦UsersRepository自身逻辑,模拟所有外部依赖,保证测试速度和稳定性,无需真实数据库。
  • 集成测试:测试模块间真实交互和数据库操作,使用独立的测试数据库,避免污染生产环境数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:25:38