NBehave Experiment: MonoRail Controllers & Rhino Mocks


…whoever keeps His word, truly the love of God is perfected in him. By this we know that we are in Him. — 1 John 2:5

Well I decided it was finally time I took a look at NBehave as another one of those tools to have in my toolbox.  NBehave’s primary use seems to be in the domain model for implementing stories from the user’s perspective, which obviously sounds very powerful. 

But I specifically wanted to see how NBehave would “behave” (sorry…) for driving out my MonoRail controllers along with mock objects for mocking out the back-end services as I do now.  In other words, making the controller itself, the “person” interacting with the system using controller-specific terminology.

So I thought I would take one of my existing controller tests and convert it to a NBehave-type story/scenario.  Here is my original old-school test:

   1: [Test]
   2: public void Should_load_pending_order_and_shopping_cart_and_list_of_acceptable_credit_card_types()
   3: {
   4:     CheckoutOrderDTO checkoutOrderDTO = new CheckoutOrderDTO();
   5:     ShoppingCartDTO shoppingCartDTO = new ShoppingCartDTO();
   6:     IList<LookupDTO> listOfCreditCards = new List<LookupDTO>();
   7:  
   8:     using (mockery.Record())
   9:     {
  10:         PrepareController(controller, "checkout", "index");
  11:         Expect.Call(mockCheckoutService.GetOrderForCheckout()).Return(checkoutOrderDTO);
  12:         Expect.Call(mockBasketService.GetShoppingCart()).Return(shoppingCartDTO);
  13:         Expect.Call(mockCreditCardLookupService.GetAllCreditCardTypes()).Return(listOfCreditCards);
  14:     }
  15:  
  16:     using (mockery.Playback())
  17:     {
  18:         controller.Index();
  19:         Assert.AreEqual(checkoutOrderDTO, controller.PropertyBag[PB.CheckoutOrder]);
  20:         Assert.AreEqual(shoppingCartDTO, controller.PropertyBag[PB.ShoppingCart]);
  21:         Assert.AreEqual(listOfCreditCards, controller.PropertyBag[PB.AllCreditCardTypes]);
  22:     }
  23: }

 

Since I tend to be very verbose in my test naming, sometimes they can become unwieldy.  Of course every time my test names get too long, I start questioning whether the unit of code I’m testing is trying to do too much, thus violating SRP.  But in this case, I’m simply loading up 3 pieces of data from various services.  (Yes, I realize the service communication here is becoming a bit chatty, and a refactoring task is definitely on the radar, but that’s not really the focus for this post).  🙂

Anyway, I wanted to see what this test would look like as a NBehave story.  Here goes…

(sorry for the code wrappage)

   1: [Test, Story]
   2: public void Load_checkout_information()
   3: {
   4:     CheckoutOrderDTO checkoutOrderDTO = new CheckoutOrderDTO();
   5:     ShoppingCartDTO shoppingCartDTO = new ShoppingCartDTO();
   6:     IList<LookupDTO> listOfCreditCards = new List<LookupDTO>();
   7:  
   8:     Story story = new Story("Load checkout information");
   9:  
  10:     story
  11:         .AsA("controller that is responsible for performing the checkout process")
  12:         .IWant("to load the checkout information based on the current user's pending order")
  13:         .SoThat("I can edit the customer's information before submitting the order");
  14:  
  15:     story
  16:         .WithScenario("Order is ready to be checked out")
  17:  
  18:             .Given("controller is prepared", delegate { PrepareController(controller, "checkout", "index"); })
  19:                 .And("pending order is retrieved from checkout service",
  20:                     delegate { Expect.Call(mockCheckoutService.GetOrderForCheckout()).Return(checkoutOrderDTO); })
  21:                 .And("shopping cart is retrieved from shopping cart service",
  22:                     delegate { Expect.Call(mockBasketService.GetShoppingCart()).Return(shoppingCartDTO); })
  23:                 .And("list of acceptable credit card types are retrieved from credit card lookup service",
  24:                     delegate { Expect.Call(mockCreditCardLookupService.GetAllCreditCardTypes()).Return(listOfCreditCards); mockery.ReplayAll(); })
  25:  
  26:             .When("the index action is executed", delegate { controller.Index(); })
  27:  
  28:             .Then("the pending order should exist in the property bag with key of", PB.CheckoutOrder,
  29:                     delegate(string key) { Assert.AreEqual(checkoutOrderDTO, controller.PropertyBag[key]); })
  30:                 .And("the shopping cart should exist in the property bag with key of", PB.ShoppingCart,
  31:                     delegate(string key) { Assert.AreEqual(shoppingCartDTO, controller.PropertyBag[key]); })
  32:                 .And("the list of accepted credit card types should exist in the property bag with key of", PB.AllCreditCardTypes,
  33:                     delegate(string key) { Assert.AreEqual(listOfCreditCards, controller.PropertyBag[key]); });
  34: }

 

But the real beauty is the generated output:

 

Theme: Checkout Process

    Story: Load checkout information
    
    Narrative:
        As a controller that is responsible for performing the checkout process
        I want to load the checkout information based on the current user's pending order
        So that I can edit the customer's information before submitting the order
    
        Scenario 1: Order is ready to be checked out
            Given controller is prepared
                And pending order is retrieved from checkout service
                And shopping cart is retrieved from shopping cart service
                And list of acceptable credit card types are retrieved from credit card lookup service
            When the index action is executed
            Then the pending order should exist in the property bag with key of: order
                And the shopping cart should exist in the property bag with key of: shoppingCart
                And the list of accepted credit card types should exist in the property bag with key of: allCreditCardTypes

</pre>
</div>
</div>


 

As the title of this post states, this is merely an experiment at this point.  But I do think it better expresses exactly what the controller is doing vs the non-NBehave test above. 

One extension I would like to add to the NBehave framework is some built-in support for mock object frameworks.  I don’t necessarily think a specific mock object framework should be integrated into NBehave, but for my own purposes, I’m thinking about adding support for the Given scope to implicitly signal to Rhino Mocks to stop recording.  As you can see, for now I had to include it in my last Given block like this:

.And("list of acceptable credit card types are retrieved from credit card lookup service",
                            delegate { Expect.Call(mockCreditCardLookupService.GetAllCreditCardTypes()).Return(listOfCreditCards); mockery.ReplayAll(); })

 

I suppose I could do something silly like this:

.And("mock object framework has stopped recording", delegate { mockery.ReplayAll(); })

 

But that would litter the nicely generated output with “test” specific terminology which I’m not very cool with.  This extension could probably be achieved using a simple decorator/interceptor, but won’t know for sure until I peek at the source.  🙂

What do you think?  Is this over-complicating things? Or could it be an elegant way for expressing what is actually going on in the controllers?

Agile Web Development with Rails – Chapter 5