如何设置最小订购量(MOQ)以根据输入更改输出参数?

3

MOQ支持设置输出参数,没有问题。我希望能够根据调用传递的内容通过Returns()或Callback()来设置参数。我的使用场景涉及模拟一个具有输出参数的方法。

这是我目前用来实验的代码,但每次都得到负面结果:

public interface ITestClass
{
    string method(string inString, out string outString);
}

public class TestClass : ITestClass
{
    public string method(string inString, out string outString)
    {
        outString = inString + " was passed in";
        return (inString + " was returned");
    }
}

[TestFixture]
public class OutTest
{
    [Test]
    public void Test()
    {
        //Arrange
        Mock<ITestClass> mock = new Mock<ITestClass>(MockBehavior.Strict);
        string stringParm = "value that will be assigned to out parameter";
        mock.Setup(t => t.method(It.IsAny<string>(), out stringParm))
            .Returns((string i, string o) =>
            {
                return i + " was returned"; // o = stringParm already
            })
            .Callback((string s, string oo) =>
            {
                stringParm = s + " was passed in"; // oo = stringParm already
            });
        TestClass real = new TestClass();
        string testString = DateTime.Now.ToLongTimeString();

        //Act
        string realOut;
        string mockOut;
        string realResult = real.method(testString, out realOut);
        string mockResult = mock.Object.method(testString, out mockOut);

        //Assert
        realResult.Should().Be(mockResult); // passes
        realOut.Should().Be(mockOut); // fails - mockout = original stringParm
    }
}

我看到这个在2009年是在MOQ基础之外实现的...但它还没有作为MOQ的一部分提供。https://code.google.com/p/moq/issues/detail?id=176 - mike mckechnie
可能是 https://dev59.com/-nNA5IYBdhLWcg3wKae3 的重复问题。 - Fabio Salvalai
@FabioSalvalai 不是,那个问题是关于如何给一个 out 或 ref 变量赋值的,我同意 MOQ 可以很好地处理。我的示例中的 stringParm 变量就是这种情况。我想要做的是在回调函数或返回块中设置 out 参数的值,使得它的值可以受到其他传递参数的影响。 - mike mckechnie
好的,明白了。我会发布一个解决方法作为建议答案。 - Fabio Salvalai
2个回答

0

我知道这不是理想的方法,但为什么不传递预期结果mockOut,而不是"将分配给输出参数的值"呢?

使用:

string stringParm = "foo was passed in"

我怀疑我没有理解你的建议 - 你介意展示一下在我上面发布的例子中哪一行代码会改变吗? - mike mckechnie
好的,我相信我做到了。将 string stringParm = "value that will be assigned to out parameter"; 替换为 string stringParm = "foo was passed in"。这样做是可行的,因为在测试中你只传递了 foo,没有其他值。 - Fabio Salvalai
我理解,但你不需要用模拟逻辑复制_Subject Under Test_的逻辑。你不需要这样做,也不应该这样做。相反,你应该定制一个特别的_Arrange_,以正确响应你的_Act_。 - Fabio Salvalai
完全同意 - 通常我也是这样做的。但在这种特定情况下,我正在尝试测试一个合同 - 我需要模拟行为,但只能在有限的可能性范围内进行。我已经解决了这个问题 - 但我仍然希望有这样的能力。我已经确认了Rhino对此的支持,所以如果需要的话,我总是可以使用它。 - mike mckechnie
嗯,模拟的目的是被调用,允许我们感知传递给它们的参数,并允许它们展示替代对象的某些行为。如果您的意思是要避免从模拟内部调用代码,那么我同意。契约测试是跨调用的行为测试。这是一种确认集成适用性而不需要进行昂贵和低效的集成测试的方法。如果您感到好奇,JB Rainsberger已经写了很多关于它的内容。 - mike mckechnie
显示剩余3条评论

0

最近我不得不更新我使用的解决方案,以便在没有参数设置的情况下执行回调。

namespace SupRep.TestUtilities
{
    using System.Reflection;
    using Moq.Language;
    using Moq.Language.Flow;

    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 delegate void OutAction<in T1,in T2,TOut>(T1 arg1, T2 arg2, 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);
        }

        public static IReturnsThrows<TMock, TReturn> OutCallback<TMock, TReturn, T1, T2, TOut>(this ICallback<TMock, TReturn> mock, OutAction<T1, T2, 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
        {
            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 });
            return mock as IReturnsThrows<TMock, TReturn>;
        }
    }}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接