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)

Mocking this using Moq is pretty straightforward:

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

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. Wrong! The assignation of _mockSub to "subword" is bound in the call to .Setup(). Everytime you call TryGetSubstitution() the value of _mockSub is set to "subword" by Moq. You can use 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() does not exist 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:

public delegate void CallbackDelegate(string p1, out string p2);
_mockSub = "subword";
_testOrthoepedia = new Mock<IOrthoepedia>(MockBehavior.Strict);
    .Setup(to => to.TryGetSubstitution(It.IsAny<string>(), out _mockSub))
    .Callback(new CallbackDelegate((string word, out string substitution) => substitution = word))

… and lo and behold: each time I called TryGetSubstitution() _mockSub was changed to the value of the first parameter passed in the call.

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<in T1, TOut>(T1 arg1, out TOut outVal);
        public delegate void OutAction<in T1, in 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 to the setting up of my tests’ mocks:

outVarSignature outVar = initialvalue;
    .Setup(to => to.MethodToMock(p1Signature, p2Signature,..., out outVar))
    .Callback(new OutAction<p1Signature, p2Signature,..., out outVarSignature>((p1Signature p1, p2Signature p2,..., out outVarSignature pN) => {callback code}))

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.

public delegate void OutAction<in T1, TOut, in T3>(in T1, out TOut outVal, in T3);
would conflict with the third delegate in the example. To avoid this the delegates could be named OutAction, InOutAction, InInOutAction, and the new one, InOutInAction.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s