如何使用最小起订量更改回调中的值?

How can I change out value in callback using Moq?

我正在尝试在 UnitTest 中模拟一些第 3 方库。它将结果数据放在 out 参数中,而 returns bool 表示是否有更多数据可用。我想测试我的组件在有两页数据时是否表现良好,但无法弄清楚如何在 Setup() 方法中更改 out 参数中的数据。我在 Linqpad 中创建了可以 运行 的极简示例:

void Main()
{
    var m = new Mock<IFoo>();

    string s = "1";
    var pg = 0;

    m.Setup(o => o.Query(out s))
        .Returns(() => pg==0)
        .Callback(() => { pg++; s = "2"; });

    IFoo f = m.Object;

    string z;
    while (f.Query(out z))
    {
        z.Dump();
    }
    z.Dump();
}

public interface IFoo {
    bool Query(out string result);
}

输出为

1
1

我该怎么做?

看起来不支持开箱即用的输出参数更改。目前我找到的最佳解决方案是基于 hack - 参见 。它使用反射调用私有方法。 使用该 hack,工作解决方案将如下所示:

void Main()
{
    var m = new Mock<IFoo>();

    string s = "1";
    var pg = 0;

    m.Setup(p => p.Query(out s))
            .OutCallback((out string v) => v = pg==0 ? "1" : "2")
            .Returns(() => pg==0)
            .Callback(() => { pg++; s = "2"; });


    IFoo f = m.Object;

    string z;
    while (f.Query(out z))
    {
        z.Dump();
    }
    z.Dump();
}

public interface IFoo {
    bool Query(out string result);
}

public static class MoqExtensions
{
    public delegate void OutAction<TOut>(out TOut outVal);
    public delegate void OutAction<in T1,TOut>(T1 arg1, out TOut outVal);

    public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, TOut>(this ICallback<TMock, TReturn> mock, OutAction<TOut> action)
        where TMock : class
    {
        return OutCallbackInternal(mock, action);
    }

    public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, T1, TOut>(this ICallback<TMock, TReturn> mock, OutAction<T1, TOut> action)
        where TMock : class
    {
        return OutCallbackInternal(mock, action);
    }

    private static IReturnsThrows<TMock, TReturn> OutCallbackInternal<TMock, TReturn>(ICallback<TMock, TReturn> mock, object action)
        where TMock : class
    {
        mock.GetType()
            .Assembly.GetType("Moq.MethodCall")
            .InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
                new[] { action });
        return mock as IReturnsThrows<TMock, TReturn>;
    }
}

是的,似乎 out 参数在一开始只使用了一次,并且连续调用无法更改它。 return 值可以像 here 中描述的那样修改,但 out 参数不能。

var outResults = new Queue<string>(new[] { "first","second","third","fourth" });
string outString = outResults.Dequeue(); 

m.Setup(o => o.Query(out outString))
    .Callback(() => 
    { 
        outString = outResults.Dequeue(); 
        outString.Dump("outString from callback");
    })
    .Returns(new Queue<bool>(new[] { true, true, false }).Dequeue);

IFoo f = m.Object;

string z;
while (f.Query(out z))
{
    z.Dump("inside while");
}
z.Dump("after while");

outString 实际上在 callback 内部被更改的次数与从 while 调用 Query 方法的次数一样多,但这并不影响out 参数值仍然是 'first'。

outString from callback

second 


inside while

first 


outString from callback

third 


inside while

first 


outString from callback

fourth 


after while

first