如何使用不同的参数多次设置相同的方法?
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
我有一个正在测试的对象,其方法称为 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