在 Moq 中分配 out/ref 参数

问题描述 投票:0回答:15

是否可以使用 Moq (3.0+) 分配

out
/
ref
参数?

我看过使用

Callback()
,但是
Action<>
不支持 ref 参数,因为它基于泛型。我还希望对
It.Is
参数的输入施加约束 (
ref
),尽管我可以在回调中做到这一点。

我知道Rhino Mocks支持此功能,但我正在开发的项目已经在使用Moq了。

c# parameters moq ref out
15个回答
401
投票

对于“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 会查看“expectedValue”的值并记住它。

对于

ref
,我也在寻找答案。

我发现以下快速入门指南很有用: https://github.com/Moq/moq4/wiki/Quickstart


193
投票

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


105
投票

编辑:在 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 在他的博客中提供了一个扩展方法,允许从回调中设置输出参数:Moq、回调和输出参数:一个特别棘手的边缘情况

这个解决方案既优雅又古怪。优雅之处在于它提供了流畅的语法,与其他 Moq 回调感觉很熟悉。而且很 hacky,因为它依赖于通过反射调用一些内部 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>;
    }
}

通过上面的扩展方法,你可以测试一个不带参数的接口,例如:

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);
}

58
投票

这是来自 Moq 网站的文档:

// 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);

22
投票

在 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);

16
投票

似乎不可能开箱即用。看起来有人尝试了解决方案

请参阅此论坛帖子 http://code.google.com/p/moq/issues/detail?id=176

这个问题 使用 Moq 验证参考参数的值


10
投票

在 VS2022 中你可以简单地做:

foo.Setup(e => e.TryGetValue(out It.Ref<ExampleType>.IsAny))
    .Returns((ref ExampleType exampleType) => {
        exampleType = new ExampleType();
        return true;
})

5
投票

我确信斯科特的解决方案一度有效,

但是不使用反射来查看私有 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);
    }

2
投票

要返回一个值并设置 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;
         }));
    }

1
投票

今天下午我为此苦苦挣扎了一个小时,但在任何地方都找不到答案。在我自己尝试之后,我找到了一个对我有用的解决方案。

string firstOutParam = "first out parameter string";
string secondOutParam = 100;
mock.SetupAllProperties();
mock.Setup(m=>m.Method(out firstOutParam, out secondOutParam)).Returns(value);

这里的关键是

mock.SetupAllProperties();
,它将为您删除所有属性。这可能不适用于每个测试用例场景,但如果您关心的只是获得
return value
YourMethod
那么这将工作得很好。


1
投票

在我简单地创建一个新的“假”类的实例(该类实现您尝试模拟的任何接口)之前,我对这里的许多建议进行了挣扎。然后您可以简单地使用方法本身设置 out 参数的值。


1
投票

以下是一个正在运行的示例。

[Fact]
public void DeclineLowIncomeApplicationsOutDemo()
{
    var mockValidator = new Mock<IFrequentFlyerNumberValidator>();

    var isValid = true; // Whatever we set here, thats what we will get.

    mockValidator.Setup(x => x.IsValid(It.IsAny<string>(), out isValid));

    var sut = new CreditCardApplicationEvaluator(mockValidator.Object);

    var application = new CreditCardApplication
    {
        GrossAnnualIncome = 19_999,
        Age = 42
    };

    var decision = sut.EvaluateUsingOut(application);

    Assert.Equal(CreditCardApplicationDecision.AutoDeclined, decision);
}

public interface IFrequentFlyerNumberValidator
{
    bool IsValid(string frequentFlyerNumber);
    void IsValid(string frequentFlyerNumber, out bool isValid);
}

请注意,设置中没有 Returs,因为没有退货。


0
投票

这可以是一个解决方案。

[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());

}

0
投票

类似的东西就可以解决问题:

被嘲笑的方法是

public bool GenerateClient(out Client client);

那么模拟部分将是:

Client client = new Client();
clintMockBll
.Setup(x => x.GenerateClient(out client))
.Returns((Client client1) =>
{
   client = new Client(){Name="Something"};
   return true;
});

0
投票

针对任何希望使用

It.IsAny
作为输出参数的人,对 Craig Celeste 的答案进行了增强:

public interface IService
{
    void DoSomething(out string a);
}

[TestMethod]
public void Test()
{
    var service = GetService();
    string actualValue;
    service.Object.DoSomething(out actualValue);
    Assert.AreEqual(expectedValue, actualValue);
}

private IService GetService()
{
    var service = new Mock<IService>();
    var anyString = It.IsAny<string>();
    service.Setup(s => s.DoSomething(out anyString))
        .Callback((out string providedString) => 
        {
            providedString = "SomeValue";
        });
    return service.Object;
}

如果您的方法还需要返回某些内容,您也可以使用

Returns
代替
Callback

© www.soinside.com 2019 - 2024. All rights reserved.