Moq:从测试中的 ref 参数取回值

Moq: getting values back from ref parameters in test

鉴于此功能参考

void ParseFirstTwoRows(FileQueue file, ref string[] firstRow, ref string[] secondRow);

如何使用 Moq 构建依赖于 firstRow 和 secondRow 具有值的测试?

我试过这个:

Mock<IFileParser> mockFileParse = new Mock<IFileParser>();

string[] firstline = new string[2] { "1", "2" };
string[] secondline = null;

mockFileParse.Setup(m => m.ParseFirstTwoRows(It.IsAny<FileQueue>(), ref firstline, ref secondline))
                .Callback<FileQueue, string[], string[]>((fq, fl, sl) =>
                {
                    fq = new FileQueue();
                    fl = firstline;
                    sl = secondline;
                });

return mockFileParse;

但这会导致错误:

Invalid callback. Setup on method with parameters (FileQueue,String[]&,String[]&) cannot invoke callback with parameters (FileQueue,String[],String[]).

我已经看到几个关于此的问题,但我很困惑是否我正在尝试做的事情是不可能的,或者我是否只是没有正确地构建测试。我的测试只需要确保 ref 参数在执行代码中具有值。

您不需要回电。只需确保在调用 SUT 时使用相同的 ref 参数,否则调用将失败。

使用您的示例代码作为参考,看看下面的示例

[TestClass]
public class FileParserTest : MiscMoqTests {
    [TestMethod]
    public void Moq_With_Ref_Arguents() {
        //Arrange
        Mock<IFileParser> mockFileParse = new Mock<IFileParser>();
        // ref arguments
        string[] firstline = new string[2] { "1", "2" };
        string[] secondline = null;
        // Only matches if the ref arguments to the invocation are the same instance
        mockFileParse.Setup(m => m.ParseFirstTwoRows(It.IsAny<FileQueue>(), ref firstline, ref secondline)).Verifiable();

        var sut = new MyDummyClass(mockFileParse.Object);

        //Act
        sut.DoSomethingWithRef(ref firstline, ref secondline);

        //Assert
        mockFileParse.Verify();
    }

    public class MyDummyClass {
        private IFileParser fileParser;

        public MyDummyClass(IFileParser fileParser) {
            this.fileParser = fileParser;
        }

        public void DoSomethingWithRef(ref string[] firstRow, ref string[] secondRow) {
            var fq = new FileQueue();
            fileParser.ParseFirstTwoRows(fq, ref firstRow, ref secondRow);

        }
    }

    public interface IFileParser {
        void ParseFirstTwoRows(FileQueue file, ref string[] firstRow, ref string[] secondRow);
    }
    public class FileQueue { }
}

但是,如果您无法为 ref 提供相同的实例,那么看起来您将无法获得成功通过的模拟。查看 Quick Start for Moq 以了解它的功能。

我通过重构解决了这个问题。虽然在这种情况下 Moq 不提供对 ref 参数的支持,但它确实可以使用 out 参数。

这是重构后的函数签名:

void ParseFirstTwoRows(FileQueue file, out string[] firstRow, out string[] secondRow);

这是测试设置:

string[] first = { "'one'", "'two'", "'three'" };
string[] second = { "'one'", "'two'", "'three'" };

Mock<IFileParser> mockFileParse = new Mock<IFileParser>();
mockFileParse.Setup(m => m.ParseFirstTwoRows(It.IsAny<FileQueue>(), out first, out second)).Verifiable();

现在,当您尝试测试依赖于 ParseFirstTwoRows 的函数时,模拟将为 out 参数提供两个指定变量。

我看到您通过使用 out 参数而不是 ref 参数解决了您的问题。

但是更改生产代码不是解决方案,您可以使用 Typemock 隔离器,它允许创建带有 ref 参数的测试:

[TestMethod,Isolated]
public void Mocking_With_Ref_Arguments()
{
    // Arrange
    string[] first = { "'one'", "'two'", "'three'" };
    string[] second = { "'one'", "'two'", "'three'" };
    FileQueue file = new FileQueue();
    var classUnderTest = new Class1();

    // What you will set as ref parameters in this line will return as you call the method
    Isolate.WhenCalled(() => classUnderTest.ParseFirstTwoRows(file, ref first, ref second)).IgnoreCall();

    // Act
    string[] retFirst = null;
    string[] retSecond = null;
    classUnderTest.someMethod(file, ref retFirst, ref retSecond);

    // Assert
    CollectionAssert.AreEquivalent(first, retFirst);
    CollectionAssert.AreEquivalent(second, retSecond);
}