2

I'm trying to mock some 3rd party lib in UnitTest. It puts result data in out parameter while returns bool signaling if more data is available. I want to test that my component behaves well when there are two pages of data but can't figure out how to change data in out parameter in Setup() method. I created minimalist sample that can be run in 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);
}

The output is

1
1

How can I do it?

eXavier
  • 4,821
  • 4
  • 35
  • 57
  • Could you post your unit test instaed the linqpad code? In the unit test the mock of the Query method will either return true when the are data of simply false when there are no data. The unit test should not contain any logic like the while loop in the linqpad example. – Daniel Dušek Feb 09 '16 at 21:59
  • Just replace `while` with `if` and instead `Dump()` you can use `Assert`. The intended expectation from calling the `Query()` method is that the first call returns `true` and `s` is set to value of `"1"` while any subsequent call to `Query()` returns `false` and `s` is set to `"2"`. The return value is as expected but the value of `out` parameter is always `"1"`. I intentionally distilled this minimalist sample, just to isolate the problem. – eXavier Feb 10 '16 at 06:01
  • The unit test should not contain any logic like ```while``` nor ```if```. Create two separate unit tests. The first one will setup the Query so it returns true and out s will be "1". In the second test setup Query so it returns false and s = "2". – Daniel Dušek Feb 10 '16 at 10:14
  • The unit test actually tests function of abstraction layer that reads data out of a data provider. Data are returned as IEnumerable of a common structure and the test ensures it returns all data even for multiple pages. – eXavier Feb 10 '16 at 11:43
  • Please post your code you would like to test. So far I understand you need to test a method called Query which has some string out paremeter and returns bool. How the IEnumerable fits to this scenatio? And post the unit test you have so far. – Daniel Dušek Feb 10 '16 at 15:37
  • Look at http://haacked.com/archive/2009/09/29/moq-sequences.aspx/. It is similar case, the main difference is that in his case the data can be extracted from reader object while in my case the data is returned in `out` parameter. So instead of his `map(reader)` I would need to call `map(the_out_value)`. I believe the expectations I set on Query() method is clear: the first call to Query() returns true and "1" in out parameter, the second call returns false and "2" in out parameter. (I can't simply paste here the full code.) – eXavier Feb 10 '16 at 16:45

2 Answers2

1

It looks like the changing of out parameters is not supported out of the box. The best solution I found so far is based on hack - see https://stackoverflow.com/a/19598345/463041. It invokes private method using reflection. Using that hack, the working solution would look like this:

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>;
    }
}
Community
  • 1
  • 1
eXavier
  • 4,821
  • 4
  • 35
  • 57
0

Yes it seems that the out parameter is used just once at the very beginning and the successive calls can't change it. The return value can be modified like described here but the out parameter can't.

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");

The outString was actually changed inside of the callback as many times as the Query method was called from while but this didn't affect the value of the out parameter which value was still 'first'.

outString from callback

second 


inside while

first 


outString from callback

third 


inside while

first 


outString from callback

fourth 


after while

first 
Daniel Dušek
  • 13,683
  • 5
  • 36
  • 51