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

