Friday, July 27, 2012

Dynamic Test Data Using Rhino.Mocks Do() Method

Scenario: You need to test a controller action that updates a set of data, then redisplays the page with that updated data. You want to stub out the code that actually executes the update so that it returns test data, but you need your stub to mimic the update functionality to prove that the updated data is getting re-displayed.

How I Did It:
The challenge here is making your test data mimic the same changes without using the actual update code. We're testing the controller here - the update code gets tested separately - and separation of concerns is a Good Thing. So how do we use arbitrary custom behavior on our mocked class when our system under test calls the update method?


Fortunately, Rhino.Mocks has anticipated this very question, and provided the Do() method for just such eventualities. Do() is an extension method that replaces the mocked/stubbed method with code that you provide at configuration. The code you provide needs to match the called method's footprint.

In my case, I was calling a web service that would maintain a calendar of excluded dates that users could not select in a date picker. The admin section  had a tool for adding new excluded dates. To hook the tool up to the web service, I needed to mock this:
class WebServiceAdapter
{
   void AddCalendarDate(string NewDate, string NewDateDescription)
   ImportantDate[] GetExcludedDates()
   List<ImportantDate> GetCalendarDates()
}
The controller would call the AddCalendarDate(), which modified the underlying data. Then, when building the model to redisplay the updated list, both GetExcludedDates() and GetCalendarDates() are called. GetExcludedDates() returned the string array of the short formatted dates to send to the DatePicker control so that those dates would be disabled. GetCalendarDates() returned the list of date objects to display in a simple table of all excluded dates.

All three needed to be mocked, but I wanted to test that the updated model was actually different from the original. My test looked like:
{
   CalendarModel testData= new CalendarModel {
       ExcludedDates = new string[] { "8/1/2012" },
       CalendarDates = new List<ImportantDate>() {
         new ImportantDate() {
           Date = DateTime.Parse("8/1/2012"),
           Description = "Test Calendar Day"
         }
       },
       NewDate = "12/25/2012",
       NewDateDescription = "Christmas Day"
   };
   ImportantDate dayToAdd = new ImportantDate()
   {
      Date = DateTime.Parse("12/25/2012"),
      Description = "Christmas Day"
   };


   mockAdapter
    .Expect(s =>

        s.AddCalendarDate(testData.NewDate, testData.NewDateDescription))
    .Return(???);
 
   mockAdapter
    .Stub(s => s.GetExcludedDates())
    .Return(???); 
 
   mockAdapter
    .Stub(s => s.GetCalendar(Arg<int>.Is.Anything))
    .Return(???); 
 
   mockAdapter.Replay();
   ViewResult result = controller.Calendar("SUBMIT", testData)
    .AssertViewRendered().ForView("Calendar");


   CalendarModel model = result.ViewData.Model as CalendarModel;
   ModelStateDictionary state = result.ViewData.ModelState;
   mockAdapter.VerifyAllExpectations();
   Assert.IsNotNull(model);
   Assert.IsTrue(state.IsValid);
   Assert.IsTrue(model.CalendarDates.Count == 2);
   Assert.AreEqual(model.CalendarDates[1].Date, dayToAdd.Date);
   Assert.AreEqual(model.ExcludedDates[1], "12/25/2012");
}
 
The question was, what could I return for those mocked methods that would give me the updated test data? Step one was to mock out the actual update. I did that by introducing the Do() method for the stub to AddCalendarDate() as:
    mockAdapter
    .Expect(s =>

       s.AddCalendarDate(testData.NewDate, testData.NewDateDescription))
    .Do((FakeAddCalendarDate)ManipulateCalendarDates);
The Do() method takes a delegate that matches the signature of the method being mocked. In this case, FakeAddCalendarDate is my delegate, declared as:
delegate void FakeAddCalendarDate(string date, string desc);
I then declared the body of my fake manipulation method within the body of my test method as:
   FakeAddCalendarDate ManipulateCalendarDates = (date, desc) =>
   {
      List<string> tempExcludes = testData.ExcludedDates.ToList();
      tempExcludes.Add(date);
      testData.ExcludedDates = tempExcludes.ToArray();
      testData.CalendarDates.Add(new ImportantDate()
      {
          Date = DateTime.Parse(date),
          Description = desc
      });
   };
C# anonymous methods using lambda expressions became a critical piece of the solution here, as you can see. Because testData is a local variable, this had to be declared inside the test method. I don't particularly care for this, but I didn't see an easy way around it within the constraints of the delegate needing to match the method signature of the mocked method, yet needing to manipulate the local variable. Moving the local outside the test method seemed just as much of a smell as this approach.

So this took care of the manipulation piece of the puzzle. Now how do I get the manipulated data back from my mocks?

At first, I tried to use the Return() method as evidenced above. Rhino.Mocks has a hidden gotcha here that makes this approach non-viable. The Return() method loads the value it is going to use at compile time. This value remains static regardless of the manipulation I was doing here. This discovery was marked by significant frustration levels on my part. How could I get around this limitation?

The answer was in following the same pattern for the two method calls to retrieve the data. I had to use custom code in mocking the dynamic get methods as well as what I'd done for the manipulation method. I needed two more delegate declarations:
delegate string[] FakeGetExcludedDates();
delegate List<TaxCalendar> FakeGetCalendarDates(int year = 0);
 I then implemented the delegates (also within the test method):
FakeGetExcludedDates getTestExcludedDates =
    () => { return testData.ExcludedDates; };
FakeGetCalendarDates getTestCalendarDates = 

   (n) => { return testData.CalendarDates; };
 Then, I updated my stubs:

mockAdapter
    .Stub(s => s.GetExcludedDates())
    .Do(getTestExcludedDates);
mockAdapter
    .Stub(s => s.GetCalendar(Arg<int>.Is.Anything))
    .Do(getTestCalendarDates);

And my tests ran green. :-)

Stay tuned for the next episode when we hear The Agile .Netter say, "We estimated the work on this project at two months, whaddaya mean you want it in a week?!?!!?"

No comments:

Post a Comment