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

单元测试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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:20:55