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

如何Mock TableClient.QueryAsync()实现按参数返回不同测试结果?

问题:Moq模拟TableClient.QueryAsync时无法按参数返回对应结果

待测试方法

public async Task<SomeClass> GetAsync(string partitionKey, string rowKey)
{
    var entities = new List<SomeClass>();
    await foreach (var e in _tableClient.QueryAsync<SomeClass>(x => x.PartitionKey == partitionKey && x.RowKey == rowKey))
    {
        entities.Add(e);
    }
    return entities.FirstOrDefault();
}

尝试的Mock实现

var thingsToMock = new List<(string PartitionKey, string RowKey, string Value)>() {
    ("maxCount", "maxCount", "0"),
    ("maxCount", "xyz", "1000"),
    ("maxCount", "abc", "2000")
};
var tableClientMock = new Mock<TableClient>();
foreach (var thingToMock in thingsToMock)
{
    var returnPage = Page<SomeClass>.FromValues(new List<SomeClass>
    {
        new SomeClass{ PartitionKey = thingToMock.PartitionKey, RowKey = thingToMock.RowKey, Value = thingToMock.Value }
    }, null, new Mock<Response>().Object);
    var returnPages = AsyncPageable<SomeClass>.FromPages(new[] { returnPage });
    Expression<Func<SomeClass, bool>> exp = (x) => x.PartitionKey == thingToMock.PartitionKey && x.RowKey == thingToMock.RowKey ? true : false;
    tableClientMock
        .Setup(i => i.QueryAsync<SomeClass>(It.Is<Expression<Func<SomeClass, bool>>>(expression => LambdaExpression.Equals(expression, exp)), null, null, default))
        .Returns(returnPages);
}

遇到的问题

  • 调用GetAsync("maxCount", "abc")时抛出Object reference not set to an instance of an object错误
  • 改用It.IsAny<Expression<Func<SomeClass, bool>>>()虽能运行,但无法根据不同的partitionKey和rowKey返回对应结果
  • 使用的NuGet版本:
    • Azure.Data.Tables 12.7.1
    • Moq 4.14.1

解决方案

问题根源

LambdaExpression.Equals判断的是表达式实例的引用或结构完全一致性,但GetAsync中动态生成的表达式和Setup里定义的exp是不同实例,导致Mock匹配失败,QueryAsync返回null引发空引用异常。

实现步骤

  1. 编写自定义表达式匹配器:解析表达式中的PartitionKey和RowKey比较值,与预期参数对比
  2. 修复循环闭包问题:捕获当前循环变量,避免所有Setup引用最后一个迭代项
  3. 重新配置Mock:使用自定义匹配器替代表达式实例相等判断

完整代码

自定义匹配方法

private static bool MatchesQueryExpression(Expression<Func<SomeClass, bool>> expression, string expectedPartitionKey, string expectedRowKey)
{
    if (expression.Body is BinaryExpression andExpression && andExpression.NodeType == ExpressionType.AndAlso)
    {
        var leftCondition = andExpression.Left as BinaryExpression;
        var rightCondition = andExpression.Right as BinaryExpression;

        // 验证左侧是PartitionKey的相等判断
        if (leftCondition?.Left is MemberExpression leftMember && leftMember.Member.Name == nameof(SomeClass.PartitionKey)
            && leftCondition.NodeType == ExpressionType.Equal && leftCondition.Right is ConstantExpression leftValue)
        {
            // 验证右侧是RowKey的相等判断
            if (rightCondition?.Left is MemberExpression rightMember && rightMember.Member.Name == nameof(SomeClass.RowKey)
                && rightCondition.NodeType == ExpressionType.Equal && rightCondition.Right is ConstantExpression rightValue)
            {
                return leftValue.Value.ToString() == expectedPartitionKey 
                    && rightValue.Value.ToString() == expectedRowKey;
            }
        }
    }
    return false;
}

修正后的Mock配置

var thingsToMock = new List<(string PartitionKey, string RowKey, string Value)>() {
    ("maxCount", "maxCount", "0"),
    ("maxCount", "xyz", "1000"),
    ("maxCount", "abc", "2000")
};
var tableClientMock = new Mock<TableClient>();

foreach (var thingToMock in thingsToMock)
{
    var currentMock = thingToMock; // 捕获当前循环变量,解决闭包问题
    var returnPage = Page<SomeClass>.FromValues(new List<SomeClass>
    {
        new SomeClass{ PartitionKey = currentMock.PartitionKey, RowKey = currentMock.RowKey, Value = currentMock.Value }
    }, null, new Mock<Response>().Object);
    var returnPages = AsyncPageable<SomeClass>.FromPages(new[] { returnPage });

    tableClientMock
        .Setup(i => i.QueryAsync<SomeClass>(
            It.Is<Expression<Func<SomeClass, bool>>>(expr => MatchesQueryExpression(expr, currentMock.PartitionKey, currentMock.RowKey)),
            null, null, default))
        .Returns(returnPages);
}

关键说明

  • 自定义匹配器通过解析表达式的逻辑结构,提取实际传入的PartitionKey和RowKey常量值,确保匹配逻辑正确
  • 循环中使用currentMock捕获变量,避免闭包导致所有Setup都绑定到最后一个循环项
  • 该方案可以精准匹配不同参数组合的QueryAsync调用,返回对应的模拟结果

内容的提问来源于stack exchange,提问作者stack247

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 10:05:32