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

Time:10-25

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

  • Related