views:

197

answers:

6

We have just released a re-written(for the 3rd time) module for our proprietary system. This module, which we call the Load Manager, is by far the most complicated of all the modules in our system to date. We are trying to get a comprehensive test suite because every time we make any kind of significant change to this module there is hell to pay for weeks in sorting out bugs and quirks. However, developing a test suite has proven to be quite difficult so we are looking for ideas.

The Load Manager's guts reside in a class called LoadManagerHandler, this is essentially all of the logic behind the module. This handler calls upon multiple controllers to do the CRUD methods in the database. These controllers are essentially the top layer of the DAL that sits on top and abstracts away our LLBLGen generated code.

So it is easy enough to mock these controllers, which we are doing using the Moq framework. However the problem comes in the complexity of the Load Manager and the issues that we receive aren't in dealing with the simple cases but the cases where there is a substantial amount of data contained within the handler.

To briefly explain the load manager contains a number of "unloaded" details, sometimes in the hundreds, that are then dropped into user created loads and reship pools. During the process of creating and populating these loads there is a multitude of deletes, changes, and additions that eventually cause issues to appear. However, because when you mock a method of an object the last mock wins, ie:

jobDetailControllerMock.Setup(mock => mock.GetById(1)).Returns(jobDetail1);
jobDetailControllerMock.Setup(mock => mock.GetById(2)).Returns(jobDetail2);
jobDetailControllerMock.Setup(mock => mock.GetById(3)).Returns(jobDetail3);

No matter what I send to jobDetailController.GetById(x) I will always get back jobDetail3. This makes testing almost impossible because we have to make sure that when changes are made all points are affected that should be affected.

So, I resolved to using the test database and just allowing the reads and writes to occur as normal. However, because you can't(read: should not) dictate the order of your tests, tests that are run earlier could cause tests that run later to fail.

TL/DR: I am essentially looking for testing strategies for data oriented code that is quite complex in nature.

A: 

I've never used Moq, but it seems that it should be able to match a mock invocation by argument(s) supplied.

A quick look at the Quick Start documentation has the following excerpt:

//Matching Arguments

// any value
mock.Setup(foo => foo.Execute(It.IsAny<string>())).Returns(true);


// matching Func<int>, lazy evaluated
mock.Setup(foo => foo.Add(It.Is<int>(i => i % 2 == 0))).Returns(true); 


// matching ranges
mock.Setup(foo => foo.Add(It.IsInRange<int>(0, 10, Range.Inclusive))).Returns(true);

I think you should be able to use the second example above.

Seb Rose
Thank you for your response. I have tried multiple different ways, but ultimately this is what I found (In the remarks section):http://www.clariusconsulting.net/labs/moq/html/3BFDD309.htm
joshlrogers
@josh - wow, that link's misleading. I'm going to send feedback to the author on that.
TrueWill
@josh - I think the link you're looking at really says "if there are two expectations that match the last one wins". In your case, if you use the It.Is matcher, then there will be no conflict.
Seb Rose
+1  A: 
TrueWill
I upvoted your answer because I didn't know that was possible, but I don't believe that will entirely achieve the results I am looking for. I need to return specific data based on the argument that is used and this method will not necessarily be called in a predetermined order. Thank you very much though, this will help me elsewhere.
joshlrogers
Ok....any thoughts as to why this is not working for me then? I am using the latest version of Moq and no matter what argument I pass in I receive the last mocked return result.
joshlrogers
@josh - No idea. I tested it with v3.1.416.3, which is the latest non-beta version from the site. This is the standard behavior, which is why the It.IsAny syntax was introduced to override it when necessary. I'd suggest writing the simplest test you can with your controller and see if you can eliminate the variables. You can also post on Moq Discussions - they're very helpful. http://groups.google.com/group/moqdisc
TrueWill
P.S. If you find the answer elsewhere, please post it here. You can always answer your own question! :)
TrueWill
I am assuming since you got it to work that it must be a problem with my code. I will try again and if I find what I did wrong I'll let everyone know. Thanks very much for your help!
joshlrogers
A: 

A simple testing technique is to make sure everytime a bug is logged against a system, make sure a unit test is written covering that case. You can build up a pretty solid set of tests just from that technique. And even better you won't run into the same thing twice.

Nathan Feger
A: 

No matter what I send to jobDetailController.GetById(x) I will always get back jobDetail3

You should spend more time debugging your tests because what is happening is not how Moq behaves. There is a bug in your code or tests causing something to misbehave.

If you want to make repeated calls with the same inputs but different outputs you could also use a different mocking framework. RhinoMocks supports the record/playback idiom. You're right this is not always what you want with regards to enforcing call order. I do prefer Moq myself for its simplicity.

Frank Schwieterman
+1  A: 

As noted by Seb, you can indeed use a range matching:

controller.Setup(x => x.GetById(It.IsInRange<int>(1, 3, Range.Inclusive))))).Returns<int>(i => jobs[i]);

This code uses the argument passed to the method to calculate which value to return.

kzu
+1  A: 

It sounds like LoaderManagerHandler does... quite a bit of work. "Manager" in a class name always somewhat worries me... from a TDD standpoint, it might be worth thinking about breaking the class up appropriately if possible.

How long is this class?

kyoryu
LoadManagerHandler does do quite a bit of work, it now comprises all of the logic for the actual Load Manager module (The name of the module is the only reason why "manager" is included in the class name). Creating this class was an attempt to remove as encompass the entire logic of this module in one location. It is approximately 1400 lines of code, so not a behemoth and a lot of that is argument validation, etc. All data access code is handled by the data layer. From what I have read this should be the optimal way to do TDD, but then again I could be mistaken.
joshlrogers
Personally, I'd consider 1400 lines to be a very large class that likely needs to be refactored to a smaller size, though not pathologically huge. More to the point than just the size, the description of the class made it sound like it had a number of responsibilities, violating the "Single Responsibility Principle" and becoming a God Class. At any rate, when I hear "the class is too complex with too many dependencies to test," my immediate reaction is always "break it up into smaller, simpler classes." The Inversion of COntrol pattern may help.
kyoryu