Using Moq to mock a method with out parameters


In my home projects I came across the need to write a unit test for a method with the following signature:

bool TryGetSubstitution(string word, out string substitution)

The method is part of the IOrthoepedia interface and is used to switch out certain substrings that are not rendered coherently by the TTS system I use.

Mocking this using Moq is pretty straightforward:

_mockSub = "subword";

_testOrthoepedia = new Mock<IOrthoepedia>(MockBehavior.Strict);
_testOrthoepedia
    .Setup(to => to.TryGetSubstitution(It.IsAny<string>(), out _mockSub))
    .Returns(true);

But the problem with this is that, while it allows the TryGetSubstitution() method to be called, it essentially does nothing! Prior to the call _mockSub is set to "subword" and after the call _mockSub is set to "subword". No change, no variation. I needed Moq to return different values for the out parameter in different calls.

“Easy,” you say: “in each test, before the call is made, just change the value of _mockSub to the desired return value.”

var _mockSub = "subword";
var _obtained = string.Empty;

var _testOrthoepedia = new Mock<IOrthoepedia>(MockBehavior.Strict);
_testOrthoepedia
    .Setup(to => to.TryGetSubstitution(It.IsAny<string>(), out _mockSub))
    .Returns(true);
var testObj = _testOrthoepedia.Object;
...
_mockSub = "subWordA";
var result = testObj.TryGetSubstitution("wordA", out _obtained);
// _obtained is set to "subwordA"
...
_mockSub = "subWordB";
result = testObj.TryGetSubstitution("wordB", out _obtained);
// _obtained is set to "subwordB"

Wrong!

When the .Setup() is called at runtime, the mock is created using the current value of _mocksub, which will be “subword“. So when each call to TryGetSubstituion() is made, the mock will return the value it has associated with the out parameter: “subword“. After each call in the above code _obtained is set to "subword", not "subWordA" or "subWordB". This behaviour could be used that to verify that TryGetSubstitution() has been called, but not to get different values back in your unit tests.

I looked online for a solution, but I found the answers both confusing, and obsolete. Among them was this one by Scott Wegner on this StackOverflow page. The code he provided looked promising and I tried it out, but it did not work. The call to InvokeMember() kept failing because SetCallbackWithArguments() no longer exists in the Moq assembly. That was disappointing.

Then, after returning to the page at least 20 times, I finally noticed the header above the code (For Moq version(sic) before 4.10), and above that he has code for later versions of Moq, like I am using (yes, I can be dense at times).

To try out this variation, I added a definition for a new delegate and changed my mock. In this case, I changed the mock so that calls to TryGetSubstitution() would return whatever value is in the first parameter:

public delegate void CallbackDelegate(string p1, out string p2);
...
var _mockSub = "subword";
var _obtained = string.Empty;

var _resultWord = string.Empty; // defined for pre-7 C#. As of .Net 7, out string _resultWord can be used.
var _testOrthoepedia = new Mock<IOrthoepedia>(MockBehavior.Strict);
_testOrthoepedia
    .Setup(to => to.TryGetSubstitution(It.IsAny<string>(), out _resultWord))
    .Callback(new CallbackDelegate((string word, out string substitution) => substitution = _mockSub))
    .Returns(true);
var testObj = _testOrthoepedia.Object;
...
_mockSub = "subWordA";
var result = testObj.TryGetSubstitution("wordA", out _obtained);
// _obtained is set to "subWordA" 
...
_mockSub = "subWordB";
result = testObj.TryGetSubstitution("wordB", out _obtained);
// _obtained is set to "subWordB"

… and lo and behold: each time I called TryGetSubstitution() _obtained was changed to the current value of _mockSub.

This works because, even though the .Setup() is still evaluated when it is called, the CallbackDelegate() is not evaluated at that time. It is evaluated when the mock object is called, and is evaluated each time it is called. Therefore the evaluation of substitution = _mockSub occurs after the value of _mockSub is changed, so it returns the current value of _mockSub, not the value when the .Setup() was evaluated.

To genericize and expand upon this in the future I have set up a new MoqDelegates class

namespace CDP.Common.Utilities.Delegates
{
    public static class MoqDelegates
    {
        public delegate void OutAction<TOut>(out TOut outVal);
        public delegate void OutAction<T1, TOut>(T1 arg1, out TOut outVal);
        public delegate void OutAction<T1, T2, TOut>(T1 arg1, T2 agr2, out TOut outVal);
    }
}

to which additional signatures can be added as needed. And to use it, I now just add the following set up to my tests’ mocks:

outVarType outVar = initialvalue;
ClassToMock
    .Setup(to => to.MethodToMock(p1Type, p2Type,..., out outVar))
    .Callback(new OutAction<p1Type, p2Type,..., outVarType>((p1Type p1, p2Type p2,..., outVarType pN) => {callback code to set out parameters}))

e.g.

string _mockSub = "subword";
var _obtained = string.Empty;

_testOrthoepedia = new Mock<IOrthoepedia>(MockBehavior.Strict);
_testOrthoepedia
    .Setup(to => to.TryGetSubstitution(It.IsAny<string>(), out string _resultWord))
    .Callback(new OutAction<string, string>((string word, out string substitution) => { substitution = _mockSub; }))
    .Returns(true);
_var testObj = _testOrthodepia.Object;
...
var result = testObj.TryGetSubstitution("wordA", out _obtained );
// _obtained is set to "wordA"

var result = testObj.TryGetSubstitution("wordB", out _obtained ); 
// _obtained is set to "wordB"

Hopefully, that will be enough for me, and perhaps someone else out there, to be able to expand upon and create whatever mock is needed for test cases involving out parameters.

Edit: Note that in the example MoqDelegates class I presented above, all the delegates are named OutAction. I have found it useful to give each a slightly different name in order to allow for possible conflicts in signatures.

e.g. Trying to create a new delegate in the MoqDelegates class where the 2nd of 3 parameters is an out parameter, like this:

public delegate void OutAction<T1, TOut, T3>(T1 arg1, TOut agr2, T3 arg3);

would conflict with the third delegate. To avoid this, the delegates could be named OutAction, InOutAction, InInOutAction, and the new one, InOutInAction.

namespace CDP.Common.Utilities.Delegates
{
    public static class MoqDelegates
    {
        public delegate void OutAction<TOut>(out TOut outVal);
        public delegate void InOutAction<T1, TOut>(T1 arg1, out TOut outVal);
        public delegate void InInOutAction<T1, T2, TOut>(T1 arg1, T2 agr2, out TOut outVal);
        public delegate void InOutInAction<T1, TOut, T3>(T1 arg1, TOut agr2, T3 arg3);
    }
}

Edit: If you saw previous versions of this post, you may notice that I have removed the in parameter modifier from the delegate declarations. I was so focused on getting this construct to work that I never noticed that the in modifiers were actually there. Since they do change the way that the methods operate, I decided it would be prudent to remove them from the examples. Users can add them back when the parameter needs to be passed by reference.

Edit: In showing how to use the MoqDelegates class, I incorrectly included the out keyword before the generic type specification of the out parameter. I have now removed it.

Edit: I have changed the examples to make them, hopefully, clearer.

Leave a comment