4

I'm having some issues setting up one of my mocks in a test that will ultimately call the setup many times but with different parameters so:

var testMock = new Mock<SomeClass>(MockBehavior.Strict);

for (int i = 30000; i <= 300000; i+=10000)
{
   testMock.Setup(x => x.MethodA(SomeStaticClass.GetIt(varA, varB, i), It.IsAny<int>()))
       .Returns(new List<SomeClass>());
}

So, the above doesn't work. It seems like only the last iteration will be "remembered" by the mock. How can I set up many setups on one mock like I intended to do above?

Force444
  • 3,321
  • 9
  • 39
  • 77
  • 6
    Probably your `i` is just captured in closure. Try `int tmp = i; ... SomeStaticClass.GetIt(varA,varB,tmp)` – Evk Feb 13 '18 at 09:05
  • @Evk It works thanks. But I don't understand why. Could you explain in an answer please and I would be happy to accept it. Thanks. – Force444 Feb 13 '18 at 09:10
  • See [Captured Closure (Loop Variable) in C# 5.0](https://stackoverflow.com/questions/16264289/) and its linked threads. – Jeppe Stig Nielsen Feb 17 '18 at 08:44

2 Answers2

7

When you use external variable from inside lambda expression - it becomes "captured" by this lambda expression and its lifetime is extended. In this example lambda expression is what you pass to Setup call, and external variable is i loop variable. It's lifetime should be extended outside of for loop, because you have no idea what Setup is going to do with that variable - it might use it for a long time after for loop and even encosing function ends. So compiler replaces this local variable with a field in compiler-generated class. Now, all lambdas you passed to Setup in your loop reference exactly the same location - field of compiler-generated class with which i variable was replaced.

When you call mocked function - Moq will compare argument you passed with available setups. But since all setups reference to the same location - they all reference to the last value of i, which it had at the end of the loop.

When you copy loop variable to another variable:

for (int i = 30000; i <= 300000; i+=10000)
{
   var tmp = i;
   testMock.Setup(x => x.MethodA(SomeStaticClass.GetIt(varA, varB, tmp), It.IsAny<int>()))
       .Returns(new List<SomeClass>());
}

It also gets captured and its lifetime is extended, but this time each loop iteration has it's own variable, so it's own instance of compiler-generated class, and now all Setup lambdas use different value.

Evk
  • 98,527
  • 8
  • 141
  • 191
1

This can be done without the loop by using the It.* argument matchers for whatever value(s) that static class returns.

So let us assume the mocked MethodA expects values between 300 to 3000. Then It.IsInRange would setup the mock to expect and handle that range

testMock
    .Setup(_ => _.MethodA(It.IsInRange<int>(300, 3000, Range.Inclusive)), It.IsAny<int>()))
    .Returns(new List<SomeClass>());

Also, reference Moq Quickstart to get a better understanding of how to use the mocking library.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Yes but I do care about that specific parameter. So that it is being invoked from 30000 with 10000 increments up to 300000. With It.IsAny the test would still pass if 'i' suddenly was 20000 wouldn't it? – Force444 Feb 13 '18 at 11:47
  • 1
    You can specify a range for the matcher as well. What would be the range of the expected values to be passed? Check the docs – Nkosi Feb 13 '18 at 11:49
  • Ah yes I missed that. Doing a range is perfect for my scenario – Force444 Feb 13 '18 at 11:53