Tuesday, May 29, 2012

Moq - Avoiding optimized return results.

Recently I was putting together a unit test for a MVVM+C Controller that accessed a VM factory that I was mocking out. This was to address a bug where multiple calls for the same domain object would result in multiple VM instances rather than references to the same instance. (Something I hadn't handled and spotted with some odd UI behaviour.)

Unfortunately I had to eat my dogfood with this bug because I went and fixed it before I had a unit test to reproduce it. My fix appeared to work, and I wrote a unit test that asserted it, but I wanted to be sure it covered the original bug, so I reverted the fix, but the test still passed! Hmm, I verified that the bug was happening at runtime through the UI, and the test was pretty basic. (Kick off the re-creation of the VMs in a particular way, and check two VM references to see if they're the same or not.) I finally tracked it down to Moq doing something unexpected (though likely by design...)

Here's an example of the problematic statement:

IParticipantViewModelFactory mockParticipantViewModelFactory = new Mock();

mockParticipantViewModelFactory.Setup( pvmf => pvmf.Build( stubDtos[0] ) )
.Returns( new ParticipantFullViewModel( stubDtos[0]) );

It's an innocent enough factory mock, wouldn't one expect that each call would return a new participant VM reference? After eliminating other possible issues with my test setup I knocked in the following sanity check:

var test1 = mockParticipantViewModelFactory.Object.Build(stubDtos[0]);
var test2 = mockParticipantViewModelFactory.Object.Build(stubDtos[0]);
Assert.AreNotSame(test1, test2);

Surprisingly the Assert failed?! Two calls to the Mock containing a .Return(new...) returned the same reference. (Where the real factory would have returned references to two distinct objects.)

The solution was to be a little less lazy with the mock definition:

mockParticipantViewModelFactory.Setup( pvmf => pvmf.Build( stubDtos[0] ) )
.Returns( (IFullDto dto) => new ParticipantFullViewModel( dto ) );
Now the above test, even passing in the exact same DTO returns references to two distinct View Models. 

It would appear that the Moq Mock optimized the initial "static" return into a single reference for all .Return calls, where by specifying that it should use the value from the Setup (even though it's always exactly the same) it actually builds a return value for each call.

It was an amusing behaviour to track down. I guess you could ask why did I simplify that mock return like that in the first place? it doesn't look like a very effective mock. The answer was because in this test case I don't care about the "guts" of how the view model that was being set up, I already have unit tests that assert that the VM Factory composes valid VMs, and that the controller composes those VMs through the factory correctly. This test case was for a specific bug where the factory the application was using returned 2 VMs for the same domain object, and that the application controller should handle using an existing reference if it has one.

*Edit: And in deciding to write this up to the Moq team it dawns on me why this did what it did.... I told it to return an object on that call by declaring:
.Returns( new ParticipantFullViewModel( stubDtos[0]) )
when in fact I should have written it as:
.Returns(() => new ParticipantFullViewModel( stubDtos[0]) )


No comments:

Post a Comment