【问题标题】:Assigning out/ref parameters in Moq在 Moq 中分配 out/ref 参数
【发布时间】:2023-03-31 23:34:01
【问题描述】:

是否可以使用 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);

标签: c# parameters moq ref out


【解决方案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 参数”,所以我想确定一下。
        猜你喜欢
        • 1970-01-01
        • 2012-03-01
        • 2016-08-07
        • 2011-07-26
        • 1970-01-01
        • 2012-08-08
        • 2018-12-29
        • 2021-07-03
        相关资源
        最近更新 更多