Edit: This post is actually about a weird NMock error message. As Nigel Thorne so kindly pointed out, this way of testing an IEnumerable is kind of awkward and cumbersome, as I should have just done something like this:
1 2 3 |
Stub.On(someObjectContainingOccurrences) .Method("GetOccurrences") .Will(Return.Value(new DateTime?[]{ /* some values in here */ })); |
Please use this approach, unless your test subject is actually the implementation of the
foreach construct ๐
Original post:
A recent problem I had was when I attempted to mock a function returning an
IEnumerable<T>. I went about and punched in somthing that I expected to work – something like this:
1 2 3 4 5 6 7 8 9 10 11 |
var enumerator = mocks.NewMock<IEnumerator<DateTime?>>(); Stub.On(enumerator).Method("Dispose"); var enumerable = mocks.NewMock<IEnumerable<DateTime?>>(); Stub.On(enumerable) .Method("GetEnumerator") .Will(Return.Value(enumerator)); Expect.Once.On(someObjectContainingOccurrences) .Method("GetOccurrences") .Will(Return.Value(enumerable)); |
– and then I set some expectations on the usage:
1 2 3 4 |
Expect.Once.On(enumerator).Method("MoveNext"); Expect.Once.On(enumerator).GetProperty("Current").Will(Return.Value((DateTime?)new DateTime(2003, 11, 12))); Expect.Once.On(enumerator).Method("MoveNext"); // [...] stuff like that... |
– and the usage inside my class under test was something like this:
1 2 3 4 |
foreach(var occurrence in someObjectContainingOccurrences.GetOccurrences()) { // do stuff to the occurrence in here } |
– but then I ran the test, and I got this cryptic error message:
1 2 3 4 5 6 7 8 |
System.Runtime.Remoting.RemotingException: ByRef value type parameter cannot be null. at System.Runtime.Remoting.Proxies.RealProxy.ValidateReturnArg(Object arg, Type paramType) at System.Runtime.Remoting.Proxies.RealProxy.PropagateOutParameters(IMessage msg, Object[] outArgs, Object returnValue) at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(ref MessageData msgData, Int32 type) at System.Collections.IEnumerator.MoveNext() at System.Collections.IEnumerator.MoveNext() [...] |
This was really annoying! Especially due to the fact that the foreach prevented me from seeing what was actually going on. It took me a few minutes to realize that the error came from me being too sloppy to check out the IEnumerator interface I was mocking – the interface looks like this:
1 2 3 |
void Reset(); bool MoveNext(); object Current { get; } |
(where the generic IEnumerator<T> interface inherits from IEnumerator, narrowing the return type of the Current property down to objects of type T).
Can you spot the error?
The return type of MoveNext is bool!!!
When I am mocking a method call like this:
1 |
Expect.Once.On(enumerator).Method("MoveNext"); |
NMock will check the method signature – and if the signature has a return type other than void, NMock tries to be nice and – instead of emitting an error (which IMO would be appropriate) – returns null! And since null is a reference type, but the expected type was bool, I got the cryptic RemotingException.
The solution was obvious:
1 |
Expect.Once.On(enumerator).Method("MoveNext").Will(Return.Value(true)); |
PS: I am posting this to avoid having this problem again (for too long). I remember having had this error before, but it took me almost 30 minutes to realize what was wrong. Hopefully, next time I will remember ๐
Hi Mookid,
The approach you have taken is the pure unit testing approach, which I take 95% of the time. The aim being, of course, to test the class in isolation.. not coupling your testing to more than one class at once.
In this specific situation however I make an exception and pass in a real list (or simple array), usually populated with mock objects. The benefit is that the tests become simpler and clearer to read. The cost is you are really testing .Net’s Array type at the same time as your code. However I am confident that their array type is working and isn’t going to cause me any problems, so I am happy with this trade off.
ps. That error message is a good one to remember. It is cryptic and as you found out means the return type has not been set for your expectation.
Thanks for your comment Nigel – and thanks for pointing out that I am jumping through hoops here… I usually do use Lists and Arrays for that purpose, but somehow that did not occur to me for this particular test ๐