-
-
Save AdrienPoupa/9d9cb6ea5aa4be6cdcf2a6ed1e0eb1f7 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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment