C# 模拟 Mock<StreamWriter>

C# mocking Mock<StreamWriter>

我是 C# 模拟的新手,我正在尝试阅读一些代码,但其中一个测试失败了,你能向我解释一下下面的源代码试图测试什么以及它什么时候会失败吗?

Mock<StreamWriter> _streamWriterMock;
string[] expectedLines;
.
.
.
foreach (var line in expectedLines)
{
    _streamWriterMock.Verify(a => a.Write(line), Times.Exactly(1));
}

示例中的测试遍历 expectedLines 数组中的所有字符串,并检查 _streamWriterMock.Write(string value) 是否对每个字符串调用了一次。如果 Write 未被调用或在任何字符串上被多次调用,它将失败。

更新

通常模拟的方法必须是虚拟的,并且根据您的模拟框架,模拟的方法可能需要在调用之前设置,因此它可能根本不是有效的测试,因为 StreamWriter 是一个具体的 class 并且Write 不是虚方法。

鉴于嘲笑 StreamWriter

充其量,foreach 循环用于通过模拟验证预期行数组中的每个字符串在模拟流编写器的 Write 方法上被调用一次正在测试的主题。

如果任何预期的行被写入多次,测试将失败。

评论Moq: Quickstart - Verification

以下面的class为例,可能的主题依赖于StreamWriter

public class SubjectUnderTest {
    private StringWriter stringWriter;

    public SubjectUnderTest(StringWriter stringWriter) {
        this.stringWriter = stringWriter;
    }

    public void WriteLines(string[] lines) {
        foreach (var line in lines) {
            this.stringWriter.Write(line);
        }
    }
}

测试时会模拟依赖关系,并且可以单独验证被测方法的功能。

例如

[TestMethod]
public void TestMethod1() {
    //Arrange
    var _streamWriterMock = new Mock<StringWriter>();
    string[] expectedLines = new[] { "line1", "line2" };
    var subject = new SubjectUnderTest(_streamWriterMock.Object);

    //Act
    subject.WriteLines(expectedLines);

    //Assert
    foreach (var line in expectedLines) {
        _streamWriterMock.Verify(a => a.Write(line), Times.Exactly(1));
    }
}

但是,如果 expectedLines 有像 { "lineN", "lineN" } 这样的重复项,那么上面的测试将失败,因为验证期望 Write 方法使用给定的字符串值被调用一次.

验证
您可能想检查被测方法是否被调用,甚至该方法被调用了多少次

只是为了重现问题,请尝试此代码

class Program
{
    static void Main(string[] args)
    {
        var _streamWriterMock = new Mock<StreamWriter>("output.txt");
        string[] expectedLines= new []{"test","test"};

        foreach (var expectedLine in expectedLines)
        {
            _streamWriterMock.Object.Write(expectedLine);
        }
        foreach (var line in expectedLines)
        {
           _streamWriterMock.Verify(a=>a.Write(line),Times.Exactly(1));    
        }

    }
}

事实上,如果您尝试使用数组 {"test","test"} 模拟您的代码,您将得到一个异常
Expected invocation on the mock exactly 1 times, but was 2 times: a => a.Write("test")

但是如果你的数组是这样的

string[] expectedLines= new []{"test","test1"};

你的 mock 将被正确执行

因此您的 verify 将检查您的方法是否针对同一输入被恰好调用了一次。 我认为代码的主要目标是省略两次写入相同的输出。