在 Moq 中分配 out/ref 参数
Posted
技术标签:
【中文标题】在 Moq 中分配 out/ref 参数【英文标题】:Assigning out/ref parameters in Moq 【发布时间】:2010-11-07 06:34:52 【问题描述】:是否可以使用 Moq (3.0+) 分配 out
/ref
参数?
我看过使用 Callback()
,但 Action<>
不支持 ref 参数,因为它基于泛型。我还希望在 ref
参数的输入上设置一个约束 (It.Is
),尽管我可以在回调中这样做。
我知道 Rhino Mocks 支持此功能,但我正在进行的项目已经在使用 Moq。
【问题讨论】:
本问答是关于 Moq 3。Moq 4.8 对 by-ref 参数的支持有了很大改进,从It.IsAny<T>()
-like 匹配器 (ref It.Ref<T>.IsAny
) 到支持设置 @987654330 @ 和 .Returns()
通过与方法签名匹配的自定义委托类型。同样支持受保护的方法。参见例如my answer below.
您可以将 out It.Ref在 Billy Jakes awnser 的基础上,我制作了一个带有 out 参数的完全动态的模拟方法。我把它贴在这里给任何觉得有用的人。
// Define a delegate with the params of the method that returns void.
delegate void methodDelegate(int x, out string output);
// Define a variable to store the return value.
bool returnValue;
// Mock the method:
// Do all logic in .Callback and store the return value.
// Then return the return value in the .Returns
mockHighlighter.Setup(h => h.SomeMethod(It.IsAny<int>(), out It.Ref<int>.IsAny))
.Callback(new methodDelegate((int x, out int output) =>
// do some logic to set the output and return value.
output = ...
returnValue = ...
))
.Returns(() => returnValue);
【讨论】:
【参考方案2】:Moq 4.8 版(或更高版本)大大改进了对 by-ref 参数的支持:
public interface IGobbler
bool Gobble(ref int amount);
delegate void GobbleCallback(ref int amount); // needed for Callback
delegate bool GobbleReturns(ref int amount); // needed for Returns
var mock = new Mock<IGobbler>();
mock.Setup(m => m.Gobble(ref It.Ref<int>.IsAny)) // match any value passed by-ref
.Callback(new GobbleCallback((ref int amount) =>
if (amount > 0)
Console.WriteLine("Gobbling...");
amount -= 1;
))
.Returns(new GobbleReturns((ref int amount) => amount > 0));
int a = 5;
bool gobbleSomeMore = true;
while (gobbleSomeMore)
gobbleSomeMore = mock.Object.Gobble(ref a);
同样的模式适用于out
参数。
It.Ref<T>.IsAny
也适用于 C# 7 in
参数(因为它们也是 by-ref)。
【讨论】:
这是解决方案,这让您可以将任何输入作为参考,就像它对非参考输入一样。这确实是一个非常好的改进支持 同样的解决方案不适用于out
,是吗?
@ATD 部分是的。使用 out 参数声明一个委托,并使用上面的语法在回调中分配值
值得一提的是,如果你要模拟的函数有更多参数,那么回调签名应该遵循相同的模式(不仅仅是 ref/out 参数)【参考方案3】:
我确信 Scott 的解决方案在某一时刻奏效了,
但不使用反射来窥视私有 api 是一个很好的论据。现在坏了。
我能够使用委托设置参数
delegate void MockOutDelegate(string s, out int value);
public void SomeMethod()
....
int value;
myMock.Setup(x => x.TryDoSomething(It.IsAny<string>(), out value))
.Callback(new MockOutDelegate((string s, out int output) => output = userId))
.Returns(true);
【讨论】:
在这种情况下userId
是什么,这是您要模拟返回的方法的值吗?【参考方案4】:
对于“out”,以下似乎对我有用。
public interface IService
void DoSomething(out string a);
[TestMethod]
public void Test()
var service = new Mock<IService>();
var expectedValue = "value";
service.Setup(s => s.DoSomething(out expectedValue));
string actualValue;
service.Object.DoSomething(out actualValue);
Assert.AreEqual(expectedValue, actualValue);
我猜 Moq 在您调用 Setup 时会查看“expectedValue”的值并记住它。
对于ref
,我也在寻找答案。
我发现以下快速入门指南很有用: https://github.com/Moq/moq4/wiki/Quickstart
【讨论】:
我认为我遇到的问题是,没有从方法Setup
分配 out/ref 参数的方法
我没有分配 ref 参数的解决方案。此示例确实将“输出值”值分配给“b”。 Moq 不会执行您传递给 Setup 的表达式,它会对其进行分析并意识到您正在为输出值提供“a”,因此它会查看“a”的当前值并记住它以供后续调用。
当 Mocked 接口方法在具有自己引用的输出变量的不同范围内执行时(例如在另一个类的方法内部),这对我不起作用。上面给出的示例很方便因为执行发生在与模拟设置相同的范围内,但是解决所有场景太简单了。在最小起订量中对 out/ref 值的显式处理的支持很弱(正如其他人所说,在执行时处理)。
+1:这是一个有用的答案。但是:如果输出参数类型是一个类而不是像字符串这样的内置类型 - 我不相信这会起作用。今天试了一下。 mock 对象模拟调用并通过“out”参数返回 null。
@azheglov 不,它适用于任何类型的out
参数。您确定您的Setup
匹配正确吗?否则,如果你有一个松散的模拟,它可能只是在没有Setup
相关时使用默认行为。如果您想确保 Moq 不会退回到空实现,请使用 MockBehavior.Strict
,因为它决定没有 Setup
匹配。【参考方案5】:
编辑:在 Moq 4.10 中,您现在可以将具有 out 或 ref 参数的委托直接传递给回调函数:
mock
.Setup(x=>x.Method(out d))
.Callback(myDelegate)
.Returns(...);
你必须定义一个委托并实例化它:
...
.Callback(new MyDelegate((out decimal v)=>v=12m))
...
对于 4.10 之前的最小起订量版本:
Avner Kashtan 在他的博客中提供了一个扩展方法,允许从回调中设置 out 参数:Moq, Callbacks and Out parameters: a particularly tricky edge case
解决方案既优雅又老套。优雅之处在于它提供了流畅的语法,让其他 Moq 回调感到宾至如归。而且很老套,因为它依赖于通过反射调用一些内部 Moq API。
上面链接提供的扩展方法没有为我编译,所以我在下面提供了一个编辑版本。您需要为您拥有的每个输入参数创建一个签名;我提供了 0 和 1,但进一步扩展它应该很简单:
public static class MoqExtensions
public delegate void OutAction<TOut>(out TOut outVal);
public delegate void OutAction<in T1,TOut>(T1 arg1, out TOut outVal);
public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, TOut>(this ICallback<TMock, TReturn> mock, OutAction<TOut> action)
where TMock : class
return OutCallbackInternal(mock, action);
public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, T1, TOut>(this ICallback<TMock, TReturn> mock, OutAction<T1, TOut> action)
where TMock : class
return OutCallbackInternal(mock, action);
private static IReturnsThrows<TMock, TReturn> OutCallbackInternal<TMock, TReturn>(ICallback<TMock, TReturn> mock, object action)
where TMock : class
mock.GetType()
.Assembly.GetType("Moq.MethodCall")
.InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
new[] action );
return mock as IReturnsThrows<TMock, TReturn>;
通过上面的扩展方法,可以测试一个带out参数的接口,如:
public interface IParser
bool TryParse(string token, out int value);
.. 使用以下最小起订量设置:
[TestMethod]
public void ParserTest()
Mock<IParser> parserMock = new Mock<IParser>();
int outVal;
parserMock
.Setup(p => p.TryParse("6", out outVal))
.OutCallback((string t, out int v) => v = 6)
.Returns(true);
int actualValue;
bool ret = parserMock.Object.TryParse("6", out actualValue);
Assert.IsTrue(ret);
Assert.AreEqual(6, actualValue);
编辑:要支持 void-return 方法,您只需添加新的重载方法:
public static ICallbackResult OutCallback<TOut>(this ICallback mock, OutAction<TOut> action)
return OutCallbackInternal(mock, action);
public static ICallbackResult OutCallback<T1, TOut>(this ICallback mock, OutAction<T1, TOut> action)
return OutCallbackInternal(mock, action);
private static ICallbackResult OutCallbackInternal(ICallback mock, object action)
mock.GetType().Assembly.GetType("Moq.MethodCall")
.InvokeMember("SetCallbackWithArguments", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock, new[] action );
return (ICallbackResult)mock;
这允许测试接口,例如:
public interface IValidationRule
void Validate(string input, out string message);
[TestMethod]
public void ValidatorTest()
Mock<IValidationRule> validatorMock = new Mock<IValidationRule>();
string outMessage;
validatorMock
.Setup(v => v.Validate("input", out outMessage))
.OutCallback((string i, out string m) => m = "success");
string actualMessage;
validatorMock.Object.Validate("input", out actualMessage);
Assert.AreEqual("success", actualMessage);
【讨论】:
@Wilbert,我已经用 void-return 函数的额外重载更新了我的答案。 我一直在我们的测试套件中使用这个解决方案并且一直在工作。然而,自从更新到最小起订量 4.10 后,它不再这样做了。 看起来它在这个提交 github.com/moq/moq4/commit/… 中被破坏了。也许现在有更好的方法来做到这一点? fyi 在 Moq4 中 MethodCall 现在是设置的属性,因此上面的 OutCallbackInternal 的内容更改为var methodCall = mock.GetType().GetProperty("Setup").GetValue(mock); mock.GetType().Assembly.GetType("Moq.MethodCall") .InvokeMember("SetCallbackResponse", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, methodCall, new[] action );
@Ristogod,随着对 Moq 4.10 的更新,您现在可以将具有 out 或 ref 参数的委托直接传递给回调函数:mock.Setup(x=>x.Method(out d)).Callback(myDelegate).Returns(...);
您必须定义一个委托并实例化它:...Callback(new MyDelegate((out decimal v)=>v=12m))...;
【参考方案6】:
在我简单地创建了一个新的“假”类的实例之前,我在这里遇到了许多建议,该类实现了您尝试模拟的任何接口。然后你可以简单地用方法本身设置out参数的值。
【讨论】:
【参考方案7】:要在设置 ref 参数的同时返回一个值,这里有一段代码:
public static class MoqExtensions
public static IReturnsResult<TMock> DelegateReturns<TMock, TReturn, T>(this IReturnsThrows<TMock, TReturn> mock, T func) where T : class
where TMock : class
mock.GetType().Assembly.GetType("Moq.MethodCallReturn`2").MakeGenericType(typeof(TMock), typeof(TReturn))
.InvokeMember("SetReturnDelegate", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, mock,
new[] func );
return (IReturnsResult<TMock>)mock;
然后声明你自己的委托匹配待模拟方法的签名,并提供你自己的方法实现。
public delegate int MyMethodDelegate(int x, ref int y);
[TestMethod]
public void TestSomething()
//Arrange
var mock = new Mock<ISomeInterface>();
var y = 0;
mock.Setup(m => m.MyMethod(It.IsAny<int>(), ref y))
.DelegateReturns((MyMethodDelegate)((int x, ref int y)=>
y = 1;
return 2;
));
【讨论】:
当您无法访问将作为 y 传入的变量时,这是否有效?我有一个函数,它接受 DayOfWeek 的两个 ref 参数。我需要将这两个设置为模拟存根中的特定日期,第三个参数是模拟数据库上下文。但是委托方法只是没有被调用。看起来 Moq 期望与传递给您的“MyMethod”函数的本地 y 相匹配。这对您的示例有效吗?谢谢。【参考方案8】:今天下午我为此苦苦挣扎了一个小时,但在任何地方都找不到答案。在我自己玩弄它之后,我能够想出一个对我有用的解决方案。
string firstOutParam = "first out parameter string";
string secondOutParam = 100;
mock.SetupAllProperties();
mock.Setup(m=>m.Method(out firstOutParam, out secondOutParam)).Returns(value);
这里的关键是mock.SetupAllProperties();
,它将为您删除所有属性。这可能不适用于每个测试用例场景,但如果您只关心获得YourMethod
的return value
,那么这将正常工作。
【讨论】:
【参考方案9】:这是来自Moq site的文档:
// out arguments
var outString = "ack";
// TryParse will return true, and the out argument will return "ack", lazy evaluated
mock.Setup(foo => foo.TryParse("ping", out outString)).Returns(true);
// ref arguments
var instance = new Bar();
// Only matches if the ref argument to the invocation is the same instance
mock.Setup(foo => foo.Submit(ref instance)).Returns(true);
【讨论】:
这和Parched的回答基本一样,也有同样的限制,不能根据输入改变out值,也不能响应ref参数。 @Richard Szalay,你可能会,但你需要使用单独的“outString”参数进行单独的设置【参考方案10】:这可以是一个解决方案。
[Test]
public void TestForOutParameterInMoq()
//Arrange
_mockParameterManager= new Mock<IParameterManager>();
Mock<IParameter > mockParameter= new Mock<IParameter >();
//Parameter affectation should be useless but is not. It's really used by Moq
IParameter parameter= mockParameter.Object;
//Mock method used in UpperParameterManager
_mockParameterManager.Setup(x => x.OutMethod(out parameter));
//Act with the real instance
_UpperParameterManager.UpperOutMethod(out parameter);
//Assert that method used on the out parameter of inner out method are really called
mockParameter.Verify(x => x.FunctionCalledInOutMethodAfterInnerOutMethod(),Times.Once());
【讨论】:
这和Parched的回答基本一样,也有同样的限制,不能根据输入改变out值,也不能响应ref参数。【参考方案11】:似乎不可能开箱即用。看起来有人尝试了解决方案
查看此论坛帖子 http://code.google.com/p/moq/issues/detail?id=176
这个问题 Verify value of reference parameter with Moq
【讨论】:
感谢您的确认。实际上,我在搜索中找到了这两个链接,但也注意到 Moq 将其中一个功能列为“支持 ref/out 参数”,所以我想确定一下。以上是关于在 Moq 中分配 out/ref 参数的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 c++ 中分配 char 数组元素时,分配的字符被破坏?
JAVA里String数组在内存分配中分配的空间每个占几个字节?