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

使用Moq与XUnit测试DocumentDBRepository时遇空引用异常及Mock问题

解决DocumentDBRepository单元测试的空引用异常与Mock IDocumentQuery问题

看起来你在给DocumentDBRepository写单元测试时遇到了两个头疼的问题:空引用异常和Linq场景下Mock IDocumentQuery的难题,我来一步步帮你解决!

一、先搞定空引用异常的根源

看你贴的代码,DocumentDBRepository<T>里的client是静态私有字段,但构造函数接收了IDocumentClient参数却没给这个静态字段赋值?这大概率是空引用的罪魁祸首!

静态字段属于类本身,不是实例级别的,如果构造函数里没写client = documentClient;,后续调用任何依赖client的方法时,必然触发空引用异常。而且静态字段还会导致多实例共享同一个客户端,在单元测试场景下很容易出现状态污染,更建议改成实例字段:

// 把静态字段改成只读实例字段,避免状态污染
private readonly IDocumentClient client;
private readonly string DatabaseId;
private readonly string CollectionId;

public DocumentDBRepository(IDocumentClient documentClient, string databaseId, string collectionId)
{
    // 加空值校验,提前暴露问题
    client = documentClient ?? throw new ArgumentNullException(nameof(documentClient));
    DatabaseId = databaseId ?? throw new ArgumentNullException(nameof(databaseId));
    CollectionId = collectionId ?? throw new ArgumentNullException(nameof(collectionId));
}

另外,在单元测试里要确保用Moq创建的IDocumentClient实例已经正确注入到Repository构造函数,别传null哦。

二、Mock IDocumentQuery处理Linq查询的单元测试方案

IDocumentQuery<T>继承了IEnumerable<T>和IAsyncEnumerable<T>,Mock的时候得同时处理同步、异步枚举逻辑,还要模拟ExecuteNextAsync方法。下面是一套可复用的解决方案:

1. 封装Mock IDocumentQuery的辅助方法

写个通用辅助方法,避免重复造轮子:

private static Mock<IDocumentQuery<T>> CreateMockDocumentQuery<T>(List<T> testData)
{
    var mockQuery = new Mock<IDocumentQuery<T>>();
    
    // 处理同步Linq枚举逻辑
    var enumerator = testData.GetEnumerator();
    mockQuery.As<IEnumerable<T>>().Setup(x => x.GetEnumerator()).Returns(enumerator);
    
    // 处理异步Linq枚举逻辑(如果用到了Async Linq)
    mockQuery.As<IAsyncEnumerable<T>>()
             .Setup(x => x.GetAsyncEnumerator(It.IsAny<CancellationToken>()))
             .Returns(new AsyncEnumerator<T>(enumerator));
    
    // 模拟ExecuteNextAsync返回测试数据
    mockQuery.Setup(x => x.ExecuteNextAsync<T>(It.IsAny<CancellationToken>()))
             .ReturnsAsync(new FeedResponse<T>(testData));
    
    // 如果需要模拟无更多数据的场景,可加这行
    // mockQuery.Setup(x => x.HasMoreResults).Returns(false);
    
    return mockQuery;
}

// 辅助实现异步枚举器
public class AsyncEnumerator<T> : IAsyncEnumerator<T>
{
    private readonly IEnumerator<T> _enumerator;

    public AsyncEnumerator(IEnumerator<T> enumerator) => _enumerator = enumerator;

    public T Current => _enumerator.Current;

    public ValueTask DisposeAsync()
    {
        _enumerator.Dispose();
        return ValueTask.CompletedTask;
    }

    public ValueTask<bool> MoveNextAsync() => ValueTask.FromResult(_enumerator.MoveNext());
}

2. 编写具体单元测试用例

比如测试Repository的GetAllAsync方法(假设该方法用Linq查询DocumentDB):

[Fact]
public async Task GetAllAsync_ReturnsAllTestItems()
{
    // Arrange
    var testEntities = new List<TestEntity>
    {
        new TestEntity { Id = "1", Name = "TestItem1" },
        new TestEntity { Id = "2", Name = "TestItem2" }
    };
    
    var mockQuery = CreateMockDocumentQuery(testEntities);
    
    var mockClient = new Mock<IDocumentClient>();
    // 模拟CreateDocumentQuery返回我们的Mock查询对象
    mockClient.Setup(x => x.CreateDocumentQuery<TestEntity>(
        It.IsAny<Uri>(),
        It.IsAny<FeedOptions>()))
              .Returns(mockQuery.Object);
    
    var repository = new DocumentDBRepository<TestEntity>(
        mockClient.Object, 
        "TestDatabase", 
        "TestCollection");
    
    // Act
    var result = await repository.GetAllAsync();
    
    // Assert
    Assert.Equal(2, result.Count());
    Assert.Contains(result, item => item.Name == "TestItem1");
}

关键注意点

  • 确保CreateDocumentQuery的Setup匹配你Repository里实际调用的参数(比如Uri、FeedOptions),不确定的话用It.IsAny<>模糊匹配即可。
  • 如果Repository里用了Linq的Where/OrderBy等操作,Moq会自动处理,因为我们已经Mock了IEnumerable的枚举逻辑。
  • 分页场景下,需要多次SetupExecuteNextAsync来模拟多页数据,同时控制HasMoreResults的返回值。

内容的提问来源于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 06:48:50