Skip to content

Instantly share code, notes, and snippets.

@jeremybeavon
Created June 24, 2015 02:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jeremybeavon/36334a937d6c405831b9 to your computer and use it in GitHub Desktop.
Save jeremybeavon/36334a937d6c405831b9 to your computer and use it in GitHub Desktop.
Moq support for ref and out callbacks
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();
}
}
}
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);
}
}
@fermat8
Copy link

fermat8 commented Jun 7, 2016

Excellent implementation! Could you also provide code for your SetupIgnoreArguments() method?

@AdrienPoupa
Copy link

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).

@jeremybeavon
Copy link
Author

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