单元测试Mock SQL查询的IDocumentQuery时遇类型转换异常
解决System.InvalidCastException: 无法将EnumerableQuery转换为IDocumentQuery的问题
嘿,这个问题我之前帮好几个开发者排查过,本质是单元测试中模拟DocumentClient时的类型不匹配问题——你用内存中的EnumerableQuery来模拟,但你的Repository方法需要返回IDocumentQuery<T>,而EnumerableQuery并没有实现这个接口,所以强转失败了。
下面给你几个可行的解决方案,按推荐程度排序:
1. 自定义MockDocumentQuery类包装EnumerableQuery
最直接的方式是写一个轻量级的IDocumentQuery<T>实现,把EnumerableQuery的逻辑包进去,这样单元测试时就能返回符合要求的类型了。
示例代码:
public class MockDocumentQuery<T> : IDocumentQuery<T> { private readonly IEnumerator<T> _enumerator; public MockDocumentQuery(IEnumerable<T> items) { _enumerator = items.GetEnumerator(); } // 核心实现:处理异步分页查询 public bool HasMoreResults => false; public Task<FeedResponse<T>> ExecuteNextAsync(CancellationToken cancellationToken = default) { var hasNext = _enumerator.MoveNext(); var result = hasNext ? new List<T> { _enumerator.Current } : new List<T>(); return Task.FromResult(new FeedResponse<T>(result)); } // 实现IEnumerable<T>接口 public IEnumerator<T> GetEnumerator() => _enumerator; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); // 异步枚举器实现(如果代码用到的话) public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default) { return new AsyncEnumeratorWrapper<T>(_enumerator); } // 其他IDocumentQuery成员按需实现,非核心方法可抛出NotImplementedException public Expression Expression => throw new NotImplementedException(); public Type ElementType => typeof(T); public IQueryProvider Provider => throw new NotImplementedException(); } // 辅助类:把同步枚举器包装成异步枚举器 public class AsyncEnumeratorWrapper<T> : IAsyncEnumerator<T> { private readonly IEnumerator<T> _enumerator; public AsyncEnumeratorWrapper(IEnumerator<T> enumerator) { _enumerator = enumerator; } public ValueTask DisposeAsync() => ValueTask.CompletedTask; public ValueTask<bool> MoveNextAsync() => ValueTask.FromResult(_enumerator.MoveNext()); public T Current => _enumerator.Current; }
然后在单元测试中,模拟DocumentClient的CreateDocumentQuery方法时返回这个自定义类:
// 假设用Moq框架 var mockClient = new Mock<IDocumentClient>(); var testData = new List<YourEntity> { /* 填充你的测试数据 */ }; mockClient.Setup(c => c.CreateDocumentQuery<YourEntity>( It.IsAny<Uri>(), It.IsAny<string>(), It.IsAny<FeedOptions>())) .Returns(new MockDocumentQuery<YourEntity>(testData)); // 注入mockClient到Repository var repository = new DocumentDBRepository<YourEntity>(mockClient.Object);
2. 调整Repository的依赖抽象
如果你的业务逻辑不需要直接操作IDocumentQuery的特定方法(比如ExecuteNextAsync),可以考虑让GetQueryBySQL返回IQueryable<T>而不是IDocumentQuery<T>。这样单元测试时直接返回EnumerableQuery<T>就不会有类型转换问题了:
修改Repository的方法:
private IQueryable<T> GetQueryBySQL(string queryStr) { var uri = UriFactory.CreateDocumentCollectionUri(_databaseId, _collectionId); var feedOptions = new FeedOptions { /* 你的配置项 */ }; return _client.CreateDocumentQuery<T>(uri, queryStr, feedOptions); }
单元测试时直接返回测试数据的AsQueryable()即可:
mockClient.Setup(c => c.CreateDocumentQuery<YourEntity>( It.IsAny<Uri>(), It.IsAny<string>(), It.IsAny<FeedOptions>())) .Returns(testData.AsQueryable());
这个方式最简洁,只要业务逻辑允许,优先推荐。
3. 用Moq直接模拟IDocumentQuery
如果不想写自定义类,也可以用Moq直接MockIDocumentQuery<T>,并设置其核心成员:
var mockQuery = new Mock<IDocumentQuery<YourEntity>>(); var testData = new List<YourEntity> { /* 测试数据 */ }; var feedResponse = new FeedResponse<YourEntity>(testData); // 设置异步查询方法 mockQuery.Setup(q => q.ExecuteNextAsync<YourEntity>(It.IsAny<CancellationToken>())) .ReturnsAsync(feedResponse); mockQuery.Setup(q => q.HasMoreResults).Returns(false); // 设置IQueryable相关成员 mockQuery.As<IQueryable<YourEntity>>() .Setup(q => q.Provider) .Returns(testData.AsQueryable().Provider); mockQuery.As<IQueryable<YourEntity>>() .Setup(q => q.Expression) .Returns(testData.AsQueryable().Expression); mockQuery.As<IQueryable<YourEntity>>() .Setup(q => q.ElementType) .Returns(typeof(YourEntity)); mockQuery.As<IQueryable<YourEntity>>() .Setup(q => q.GetEnumerator()) .Returns(testData.GetEnumerator()); // 绑定到DocumentClient var mockClient = new Mock<IDocumentClient>(); mockClient.Setup(c => c.CreateDocumentQuery<YourEntity>( It.IsAny<Uri>(), It.IsAny<string>(), It.IsAny<FeedOptions>())) .Returns(mockQuery.Object);
这个方式比较繁琐,但不需要写自定义类,适合简单的临时测试场景。
内容的提问来源于stack exchange,提问作者superninja
相关产品推荐
相关产品推荐

