I have an object under test with a method called void Print(string msg)
.
The object invoke the method multiple times and passes a different message.
For example...
Strait forward of the usage:
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}");
}
}
}
When I want to test foo
method, how can setup the Mock
object to capture the different Print
methods calls?
I tried:
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();
but this of course made only the last setup effective (when count
= 3).
How can this be done?
CodePudding user response:
Another option, since in this case the mocked member has no expected behavior in a loose mock, would be to use the loop for verification instead of setup.
For example
// 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);
}
If you want to capture the actual arguments passed, then a call back can be used
// 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
CodePudding user response:
but this of course made only the last setup effective (when count = 3).
actually that is not true, if you look closely to the test output you will see that it contains 4 calls with the value of 4
!:
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")
IMyPrinter _ => _.Print("abc_4")
this is the value that count
has after the loop has ended. Since you are using a lambda expression, your count
variable is caught in closure and the last value 4
was captured. Only this value is used when the lambda expression is evaluated later on in your code when the loop has finished a long time ago. You need to create a temporaty variable and capture the index in it. And your test will run green:
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();
EDIT:
for further reading on closures I recommend this article by Jon Skeet