如何使用Moq更改回调中的值?

时间:2016-02-08 18:28:46

标签: moq

我正在尝试在UnitTest中模拟一些第三方库。如果有更多数据可用,它会将结果数据放在out参数中,同时返回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

我该怎么做?

2 个答案:

答案 0 :(得分:1)

看起来开箱即用不支持更改输出参数。到目前为止,我找到的最佳解决方案是基于黑客攻击 - 请参阅https://stackoverflow.com/a/19598345/463041。它使用反射调用私有方法。 使用该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>;
    }
}

答案 1 :(得分:0)

是的,似乎out参数一开始只使用一次,而后续调用无法更改它。可以像描述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");

outStringcallback实际上已经从Query调用while方法的次数发生了多次变化,但这并没有影响到out参数哪个值仍然是“第一个”&#39;。

outString from callback

second 


inside while

first 


outString from callback

third 


inside while

first 


outString from callback

fourth 


after while

first