如何使用不同的参数多次设置相同的方法?

How to setup the same method multiple times with different arguments?

我有一个正在测试的对象,其方法称为 void Print(string msg)

对象多次调用该方法并传递不同的消息。

例如...

海峡前沿用法:

public interface IMyPrinter
{
   void Print(string msg);
}

public class Printer : IMyPrinter
{
    public void Print(string msg)
    {
         // print to console
    }
}

public class MyObject
{
    public IMyPrinter Printer { get; set; }
    
    public void foo()
    {
        for (var count = 0; count < 4; count++)
        {
            Printer.Print($"abc_{count}");
        }
    }
}

当我想测试 foo 方法时,如何设置 Mock 对象来捕获不同的 Print 方法调用?

我试过了:

var printerMock = new Mock<IMyPrinter>(MockBehavior.Loose);
for (var count = 0; count < 4; count++)
{
    printerMock.Setup(_ => _.Print($"abc_{count}");
}

var underTest = new MyObject();
underTest.Printer = printerMock.Object;

underTest.foo();
printerMock.VerifyAll();

但这当然只使最后的设置有效(当count = 3 时)。 如何做到这一点?

but this of course made only the last setup effective (when count = 3).

实际上这不是真的,如果您仔细观察测试输出,您会发现它包含 4 个值为 4!:

的调用
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")

这是 count 在循环结束后的值。由于您使用的是 lambda 表达式,因此您的 count 变量被捕获并捕获了最后一个值 4 。当循环在很久以前完成时,稍后在您的代码中评估 lambda 表达式时,仅使用此值。您需要创建一个临时变量并在其中捕获索引。你的测试将 运行 绿色:

var printerMock = new Mock<IMyPrinter>(MockBehavior.Loose);
for (var count = 0; count < 4; count++)
{
    int i = count; // <-- this is the main change
    printerMock.Setup(_ => _.Print($"abc_{i}")); // <-- use "i" in lambda
}

var underTest = new MyObject();
underTest.Printer = printerMock.Object;

underTest.foo();
printerMock.VerifyAll();

编辑:

为了进一步阅读闭包,我推荐 this article Jon Skeet

另一种选择,因为在这种情况下,模拟成员在松散模拟中没有预期的行为,将使用循环而不是设置进行验证。

例如

// Arrange
var printerMock = new Mock<IMyPrinter>(MockBehavior.Loose);
var underTest = new MyObject();
underTest.Printer = printerMock.Object;

// Act
underTest.foo();

// Assert
for (int count = 0; count < 4; count++) {
    string msg = $"abc_{count}";
    printerMock.Verify(_ => _.Print(msg), Times.Once);
}

如果要捕获传递的实际参数,则可以使用回调

// Arrange
List<string> expected = new List<string>() { ... }; //populated
List<string> actual = new List<string>();
var printerMock = new Mock<IMyPrinter>(MockBehavior.Loose);
printerMock
    .Setup(_ => _.Print(It.IsAny<string>()))
    .Callback(string msg => actual.Add(msg));
var underTest = new MyObject();
underTest.Printer = printerMock.Object;

// Act
underTest.foo();

// Assert
//..If messages are known before hand then they can be used to assert the
//the captured messages from when the mock was invoked.
actual.Should().BeEquivalentTo(expected); //Using FluentAssertions