在 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.Isany 用于任何使用 out 参数的方法。例如:moq.Setup(x => x.Method(out It.Ref.IsAny).Returns(TValue); 【参考方案1】:

在 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&lt;T&gt;.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=&gt;x.Method(out d)).Callback(myDelegate).Returns(...); 您必须定义一个委托并实例化它:...Callback(new MyDelegate((out decimal v)=&gt;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();,它将为您删除所有属性。这可能不适用于每个测试用例场景,但如果您只关心获得YourMethodreturn 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数组在内存分配中分配的空间每个占几个字节?

RESB、RESW、RESD、RESQ在NASM中分配多少字节? [复制]

javascript 使用解构分配从数组中分配变量

Spark在实际项目中分配更多资源