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

使用FakeItEasy在C#中模拟MongoDB集合的单元测试问题

问题分析与解决办法

错误原因

FakeItEasy只能拦截虚拟方法、抽象类成员或接口成员,而MongoDB官方驱动中的MongoClient.GetDatabase是非虚拟的具体方法,直接模拟MongoClient类必然触发“非虚拟成员无法拦截”的错误。另外你当前的服务大概率直接依赖了MongoClient具体类,而非抽象接口,导致无法正确隔离真实MongoDB连接。

正确解决方案:依赖抽象接口+分层模拟

MongoDB驱动本身提供了IMongoClient、IMongoDatabase、IMongoCollection<T>这些抽象接口,通过依赖注入这些接口实现可测试性,再用FakeItEasy逐层模拟返回预设数据即可。

步骤1:重构服务层,依赖抽象接口

把服务中直接实例化MongoClient的代码改成注入IMongoClient接口:

public class BoxService
{
    private readonly IMongoCollection<Box> _boxesCollection;

    // 构造函数注入IMongoClient,而非直接new MongoClient
    public BoxService(IMongoClient mongoClient)
    {
        var database = mongoClient.GetDatabase("YourDatabaseName");
        _boxesCollection = database.GetCollection<Box>("Boxes");
    }

    public async Task<List<Box>> GetAllBoxesAsync()
    {
        return await _boxesCollection.Find(_ => true).ToListAsync();
    }
}

步骤2:编写单元测试,逐层模拟MongoDB组件

在测试中依次模拟IMongoCollection<Box>、IMongoDatabase、IMongoClient,让FindAsync返回预设的_boxes列表:

[TestFixture]
public class BoxServiceTests
{
    [Test]
    public async Task GetAllBoxesAsync_ShouldReturnPresetBoxes()
    {
        // 1. 准备预设测试数据
        var presetBoxes = new List<Box>
        {
            new Box { Id = ObjectId.GenerateNewId(), Name = "Box1" },
            new Box { Id = ObjectId.GenerateNewId(), Name = "Box2" }
        };

        // 2. 模拟IAsyncCursor,让它返回预设数据
        var mockCursor = A.Fake<IAsyncCursor<Box>>();
        A.CallTo(() => mockCursor.MoveNextAsync(A<CancellationToken>._)).Returns(true);
        A.CallTo(() => mockCursor.Current).Returns(presetBoxes);

        // 3. 模拟IMongoCollection,让FindAsync返回上面的游标
        var mockCollection = A.Fake<IMongoCollection<Box>>();
        A.CallTo(() => mockCollection.FindAsync(
            A<FilterDefinition<Box>>._,
            A<FindOptions<Box, Box>>._,
            A<CancellationToken>._))
            .Returns(Task.FromResult(mockCursor));

        // 4. 模拟IMongoDatabase,返回上面的模拟集合
        var mockDatabase = A.Fake<IMongoDatabase>();
        A.CallTo(() => mockDatabase.GetCollection<Box>("Boxes", A<MongoCollectionSettings>._))
            .Returns(mockCollection);

        // 5. 模拟IMongoClient,返回上面的模拟数据库
        var mockClient = A.Fake<IMongoClient>();
        A.CallTo(() => mockClient.GetDatabase("YourDatabaseName", A<MongoDatabaseSettings>._))
            .Returns(mockDatabase);

        // 6. 实例化服务并执行测试
        var service = new BoxService(mockClient);
        var result = await service.GetAllBoxesAsync();

        // 断言结果与预设数据匹配
        Assert.That(result.Count, Is.EqualTo(presetBoxes.Count));
        Assert.That(result.Select(b => b.Name), Is.EquivalentTo(presetBoxes.Select(b => b.Name)));
    }
}

关键说明

  • 必须依赖IMongoClient等抽象接口,而非具体的MongoClient类,FakeItEasy才能正常拦截方法调用。
  • 模拟IAsyncCursor<T>时,需要同时设置MoveNextAsync返回true(表示存在数据)和Current返回预设列表,才能让ToListAsync正确读取数据。

内容的提问来源于stack exchange,提问作者cs.kali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 16:57:29