Created
June 24, 2015 02:21
-
-
Save jeremybeavon/36334a937d6c405831b9 to your computer and use it in GitHub Desktop.
Moq support for ref and out callbacks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
using Moq; | |
using Moq.Language; | |
using Moq.Language.Flow; | |
public static class MoqExtensions | |
{ | |
private const BindingFlags privateInstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic; | |
public static ICallbackResult OutCallback(this ICallback mock, Delegate callback) | |
{ | |
RefCallbackHelper.SetCallback(mock, callback); | |
return (ICallbackResult)mock; | |
} | |
public static ICallbackResult RefCallback(this ICallback mock, Delegate callback) | |
{ | |
RefCallbackHelper.SetCallback(mock, callback); | |
return (ICallbackResult)mock; | |
} | |
public static IReturnsThrows<TMock, TResult> OutCallback<TMock, TResult>(this ICallback<TMock, TResult> mock, Delegate callback) | |
where TMock : class | |
{ | |
RefCallbackHelper.SetCallback(mock, callback); | |
return (IReturnsThrows<TMock, TResult>)mock; | |
} | |
public static IReturnsThrows<TMock, TResult> RefCallback<TMock, TResult>(this ICallback<TMock, TResult> mock, Delegate callback) | |
where TMock : class | |
{ | |
RefCallbackHelper.SetCallback(mock, callback); | |
return (IReturnsThrows<TMock, TResult>)mock; | |
} | |
private static class RefCallbackHelper | |
{ | |
private static readonly Action<object, Delegate> setCallbackWithoutArgumentsAction = CreateSetCallbackWithoutArgumentsAction(); | |
public static void SetCallback(object mock, Delegate callback) | |
{ | |
setCallbackWithoutArgumentsAction(mock, callback); | |
} | |
private static Action<object, Delegate> CreateSetCallbackWithoutArgumentsAction() | |
{ | |
ParameterExpression mockParameter = Expression.Parameter(typeof(object)); | |
ParameterExpression actionParameter = Expression.Parameter(typeof(Delegate)); | |
Type type = typeof(Mock<>).Assembly.GetType("Moq.MethodCall", true); | |
MethodInfo method = type.GetMethod("SetCallbackWithArguments", privateInstanceFlags); | |
if (method == null) | |
throw new InvalidOperationException(); | |
return Expression.Lambda<Action<object, Delegate>>( | |
Expression.Call(Expression.Convert(mockParameter, type), method, actionParameter), | |
mockParameter, | |
actionParameter).Compile(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Moq; | |
using NUnit.Framework; | |
[TestFixture] | |
public sealed class MoqExtensionsTests : MockTest | |
{ | |
private delegate void TryGetAction(out int value); | |
private delegate void IncrementAction(ref int value); | |
[Test] | |
public void Test_OutCallback_SetsValueWhenReturnTypeIsVoid() | |
{ | |
// Arrange | |
Mock<IOutActionTest> mockOutActionTest = CreateMock<IOutActionTest>(); | |
int tempValue; | |
mockOutActionTest.Setup(test => test.TryGet(out tempValue)).OutCallback(new TryGetAction((out int value) => value = 10)); | |
// Act | |
int actualValue; | |
mockOutActionTest.Object.TryGet(out actualValue); | |
// Assert | |
Assert.That(actualValue, Is.EqualTo(10)); | |
} | |
[Test] | |
public void Test_RefCallback_IncrementsValueWhenReturnTypeIsVoid() | |
{ | |
// Arrange | |
Mock<IRefActionTest> mockOutActionTest = CreateMock<IRefActionTest>(); | |
int tempValue = 1; | |
mockOutActionTest.SetupIgnoreArguments(test => test.Increment(ref tempValue)).RefCallback(new IncrementAction((ref int value) => value++)); | |
// Act | |
int actualValue = 10; | |
mockOutActionTest.Object.Increment(ref actualValue); | |
// Assert | |
Assert.That(actualValue, Is.EqualTo(11)); | |
} | |
[Test] | |
public void Test_OutCallback_SetsValueWhenReturnTypeIsNotVoid() | |
{ | |
// Arrange | |
Mock<IOutFuncTest> mockOutActionTest = CreateMock<IOutFuncTest>(); | |
int tempValue; | |
mockOutActionTest.Setup(test => test.TryGet(out tempValue)).OutCallback(new TryGetAction((out int value) => value = 10)).Returns(true); | |
// Act | |
int actualValue; | |
bool result = mockOutActionTest.Object.TryGet(out actualValue); | |
// Assert | |
Assert.That(result, Is.True); | |
Assert.That(actualValue, Is.EqualTo(10)); | |
} | |
[Test] | |
public void Test_RefCallback_IncrementsValueWhenReturnTypeIsNotVoid() | |
{ | |
// Arrange | |
Mock<IRefFuncTest> mockOutActionTest = CreateMock<IRefFuncTest>(); | |
int tempValue = 10; | |
mockOutActionTest.Setup(test => test.Increment(ref tempValue)).RefCallback(new IncrementAction((ref int value) => value++)).Returns(10); | |
// Act | |
int actualValue = 10; | |
int result = mockOutActionTest.Object.Increment(ref actualValue); | |
// Assert | |
Assert.That(result, Is.EqualTo(10)); | |
Assert.That(actualValue, Is.EqualTo(11)); | |
} | |
private static Mock<T> CreateMock<T>() where T : class | |
{ | |
return new Mock<T>(MockBehavior.Strict); | |
} | |
public interface IRefActionTest | |
{ | |
void Increment(ref int integer); | |
} | |
public interface IRefFuncTest | |
{ | |
int Increment(ref int integer); | |
} | |
public interface IOutActionTest | |
{ | |
void TryGet(out int value); | |
} | |
public interface IOutFuncTest | |
{ | |
bool TryGet(out int value); | |
} | |
} |
Many thanks for this. Even though I'm one year late, the SetupIgnoreArguments function can be emulated using the IgnoreRefMatching function from https://code.google.com/archive/p/moq/issues/176 (the RefCallback did not work, or at least I did not managed to make it work).
I found this implementation for SetupIgnoreArguments (https://gist.github.com/7Pass/1c6b329e85ca29071f42). It's better than mine was because it hacked the internal of Moq, where as this implementation doesn't. Don't know whether it support out/ref args but shouldn't be too hard to change to make it work.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Excellent implementation! Could you also provide code for your SetupIgnoreArguments() method?