devlooped / moq

The most popular and friendly mocking framework for .NET
Other
5.88k stars 803 forks source link

Why does the Setup method not work correctly if called from within a for loop? #933

Closed TommasoPieroncini closed 4 years ago

TommasoPieroncini commented 4 years ago

Why does the lower version work in setting up calls to the method (mock returns value as expected), but the top one doesn't? (Throws System.ArgumentOutOfRangeException).

moq-bug

The code in the top line and bottom lines is exactly the same (except for the indices). Ignore the fact that the Setup method isn't fully spelled out in the top line, that's just incidental.

stakx commented 4 years ago

If you need someone to look into this, please provide a runnable repro, and please state what you mean by "it does not work" (e.g. a full exception stack trace). Otherwise I'll close this issue in a few days.

My guess would be that numUsers > users.Length || numUsers > idList.Length and numUsers > 11.

TommasoPieroncini commented 4 years ago

You're right. I'll try to provide more information soon. To be clear, the loop executes correctly. The error I mentioned (ArgumentOutOfRangeException) gets thrown when the mock is queried for the first time after the setup using the loop above.

Partial stacktrace:

Test method ReadIdentities_ById threw exception: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index Stack Trace: at System.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) at System.Collections.Generic.List1.get_Item(Int32 index) --- End of inner exception stack trace --- at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments) at System.Delegate.DynamicInvokeImpl(Object[] args) at Moq.Evaluator.SubtreeEvaluator.Evaluate(Expression e) at Moq.Evaluator.PartialEval(Expression expression, Func2 fnCanBeEvaluated) at Moq.Matchers.LazyEvalMatcher.Matches(Object value) at Moq.MethodCall.Matches(ICallContext call) at System.Linq.Enumerable.LastOrDefault[TSource](IEnumerable1 source, Func2 predicate) at Moq.ExtractProxyCall.HandleIntercept(ICallContext invocation, InterceptorContext ctx, CurrentInterceptContext localctx) at Moq.Interceptor.Intercept(ICallContext invocation) at Castle.DynamicProxy.AbstractInvocation.Proceed() at Castle.Proxies.Proxy.MethodToMock() at :line 197

stakx commented 4 years ago

Also if iteration variable i gets captured by a lambda inside the loop, executing the lambda after the loop is likely to throw, as i will be equal to numUsers at that point. You could quickly check whether that's the issue by copying i to a local variable j at the beginning of the loop, then reference that in your setups instead.

TommasoPieroncini commented 4 years ago

Thank you for the pointer. It looks like this was the problem precisely. This is resolved now, have a great day!