为什么是??操作员无法使用Moq设置方法 - c#

有人可以向我解释为什么这行不通吗?

builder.Setup(b => b.BuildCommand(query ?? It.IsAny<string>())).Returns(command);

如果querynull,则将BuildCommand传递给null,而不是It.IsAny<string>()

相反,我必须这样做:

if(query == null)
    builder.Setup(b => b.BuildCommand(It.IsAny<string>())).Returns(command);
else
    builder.Setup(b => b.BuildCommand(query)).Returns(command);

与代表有关吗?

编辑-完整示例

public static void ReturnFromBuildCommand(this Mock<IQueryCommandBuilder> builder, IQueryCommand command, string query = null)
{
    if(query == null)
        builder.Setup(b => b.BuildCommand(It.IsAny<string>())).Returns(command);
    else
        builder.Setup(b => b.BuildCommand(query)).Returns(command);
}

然后我可以这样称呼它

var command = new Mock<IQueryCommand>();
var builder = new Mock<IQueryCommandBuilder>();
builder.ReturnFromBuildCommand(command.Object);

要么

string query = "SELECT Name FROM Persons;";
builder.ReturnFromBuildCommand(command.Object, query);

取决于我是否关心该参数。

参考方案

模拟的Setup方法采用一个表达式,然后Moq框架对该表达式进行解构,以确定所调用的方法及其参数。然后,它设置一个拦截器以匹配参数。

您可以在Mock源中看到此内容:

    internal static MethodCallReturn<T, TResult> Setup<T, TResult>(
        Mock<T> mock,
        Expression<Func<T, TResult>> expression,
        Condition condition)
        where T : class
    {
        return PexProtector.Invoke(() =>
        {
            if (expression.IsProperty())
            {
                return SetupGet(mock, expression, condition);
            }

            var methodCall = expression.GetCallInfo(mock);
            var method = methodCall.Method;
            var args = methodCall.Arguments.ToArray();

            ThrowIfNotMember(expression, method);
            ThrowIfCantOverride(expression, method);
            var call = new MethodCallReturn<T, TResult>(mock, condition, expression, method, args);

            var targetInterceptor = GetInterceptor(methodCall.Object, mock);

            targetInterceptor.AddCall(call, SetupKind.Other);

            return call;
        });
    }

这里的argsExpression[]类型。

(参考:https://github.com/moq/moq4/blob/master/Source/Mock.cs#L463)

args数组作为参数MethodCallReturn传递给Moq类型arguments的构造函数。该构造函数(通过基类MethodCall)使用MatcherFactory.Create生成参数匹配器。 (参考:https://github.com/moq/moq4/blob/master/Source/MethodCall.cs#L148)

这是事情开始变得有趣的地方!
在MatcherFactory.Create方法中,它尝试通过查找Expression.NodeType和/或对照已知类型(例如MatchExpression(类似于Is.Any<string>()这样的))来检查参数的表达式的类型。 。
(参考:https://github.com/moq/moq4/blob/master/Source/MatcherFactory.cs#L54)

因此,让我们退后一步。在您的特定情况下,代码query ?? Is.Any<string>()会被编译成表达式本身-像这样的丑陋混乱(由dotPeek反编译器生成):

(Expression) Expression.Coalesce((Expression) Expression.Field((Expression) Expression.Constant((object) cDisplayClass00, typeof (Extension.\u003C\u003Ec__DisplayClass0_0)), 
 FieldInfo.GetFieldFromHandle(__fieldref (Extension.\u003C\u003Ec__DisplayClass0_0.query))), 
(Expression) Expression.Call((Expression) null, (MethodInfo) MethodBase.GetMethodFromHandle(__methodref (It.IsAny)), new Expression[0]))

这就是第一个参数的样子。您可以重写代码以更好地表达Moq的外观,如下所示:

    public static void ReturnFromBuildCommand(this Mock<IQueryCommandBuilder> builder, IQueryCommand command, string query = null)
    {
        Expression<Func<IQueryCommandBuilder, IQueryCommand>> expressOfFunc = commandBuilder => (commandBuilder.BuildCommand(query ?? It.IsAny<string>()));

        var methodCall = expressOfFunc.Body as MethodCallExpression;
        var args = methodCall.Arguments.ToArray();
        var nodeType = args[0].NodeType;

        builder.Setup(expressOfFunc)
            .Returns(command);

    }

如果放置一个断点,则可以看到nodeType的值为Coalesce。现在,返回并更改为仅使用querynodeType变为MemberAccess。使用It.IsAny<string>(),并且nodeTypeCall

这解释了这三种方法之间的差异,以及为什么它的行为不如您预期的那样。说实话,关于为什么null触发的原因尚不清楚,但是MatcherFactory.CreateMatcher中出现的任何匹配器似乎都认为null是您的模拟配置的有效值。

将谓词<T>转换为Func <T,bool> - c#

我有一个包含成员Predicate的类,希望在Linq表达式中使用该类:using System.Linq; class MyClass { public bool DoAllHaveSomeProperty() { return m_instrumentList.All(m_filterExpression); } private IEnumerable&…

客户端反序列化为数组序列化字典<string,string>数据 - c#

我有一个字典,该字典使用C#中的JavaScriptSerializer进行了序列化。在客户端,我有:"{"dd049eda-e289-4ca2-8841-4908f94d5b65":"2","ab969ac2-320e-42e1-b759-038eb7f57178":"5�…

根据激活的Maven配置文件更新战争名称 - java

在pom中,我有两个配置文件。测试1测试2现在,我希望根据激活的配置文件更改战争名称。预期结果激活test1配置文件后,战争名称应为prefix-test1.war。激活test1和test2时,战争名称应为prefix-test1-test2.war。如果没有激活任何配置文件,则战争名称应为prefix.war。我的POM文件....<?xml ve…

如何使用BeautifulSoup在<tr>中捕获特定的<td> - python

尝试从nyc Wiki页面中的高中列表中获取所有高中名称。我已经写了足够多的脚本,可以让我获取包含在高中,学业和入学条件列表的表的<tr>标记中的所有信息-但是我如何才能缩小到我认为的范围内在td[0]内休息(会弹出KeyError)-只是学校的名称?到目前为止我写的代码:from bs4 import BeautifulSoup from ur…

为什么要使用Func <string>而不是string? - c#

为什么要使用Func<string>而不是string?我的问题特别是关于this回购。有问题的行是22: private static Func<string> getToken = () => Environment.GetEnvironmentVariable("GitHubToken", Enviro…