BDD with MSpec and Rhino Auto Mocks (part 3)

November 29, 2009 · 5 minute read · Tags: Agile | BDD | MSpecs | RhinoMocks

In part 1 and part 2 we covered setting up MSpec, writing our specifications and then implementing one of those specifications.

So far we’ve covered very simple tests for code which has no external dependencies and does very little. Now we’ll turn our attention to those tests which (on the face of it) are more complicated and cover code which relies on data access, email services etc.

Lets tackle the the “when asked for products matching search term” specification.

Establishing Context

As before, we’ll start by establishing the context for this test, and to cut down on duplication, I’ve moved the basic context setup to its own class.

  1. public class concern_for_product_controller
  2. {
  3.     protected static ProductController _controller;
  4.  
  5.     Establish context =
  6.         () => { _controller = new ProductController(); };
  7. }

Now we can make all of the Product Controller tests inherit this class.

  1. [Subject("Product Search")]
  2. public class when_asked_for_products_matching_search_term : concern_for_product_controller
  3. {
  4.     It should_retrieve_a_list_of_products_with_titles_containing_the_search_term;
  5.     It should_return_the_list_of_products_to_the_user;
  6. }

To implement the specification “should retrieve a list of products…” we want to simply check that the controller asks for a list of products from wherever we’re hoping to get our data from.

In the spirit of writing tests first we haven’t got any kind of data access layer yet so now we decide we need a Product Repository.

Rhino AutoMocker

To help test our repository we can introduce Rhino AutoMocker to the equation. Rhino AutoMocker is included as part of an AutoMocking library distributed with StructureMap.

To use it in your project, simply download StructureMap and reference StructureMap.AutoMocking and Rhino.Mocks in your project.

We need to wire up the automocker so we can use it in our tests. To that end we’ll change our base class.

  1. public class concern_for_product_controller
  2. {
  3.     protected static ProductController _controller;
  4.     protected static IProductRepository _productRepository;
  5.     static RhinoAutoMocker<ProductController> mocker;
  6.  
  7.     Establish context =
  8.         () =>
  9.             {
  10.                 mocker = new RhinoAutoMocker<ProductController>();
  11.                 _controller = mocker.ClassUnderTest;
  12.                 _productRepository = mocker.Get<IProductRepository>();
  13.             };
  14. }

So what’s going on here?

  1. We’ve introduced an instance of the AutoMocker for our Product Controller.
  2. To make life easier we’ve introduced a field which references the AutoMocker’s “class under test” (in this case our Product Controller).
  3. We’ve asked AutoMocker for an instance of IProductRepository and also referenced this as a handy field (which we can use in our tests).

Now we can turn our attention back to the test.

Setting Expectations

  1. static ActionResult _result;
  2.  
  3.  Because of =
  4.      () => { _result = _controller.Search("test"); };
  5.  
  6.  It should_retrieve_a_list_of_products_with_titles_containing_the_search_term =
  7.      () => _productRepository.AssertWasCalled(x => x.FindProducts("test"));

  1. We call a new overloaded version of our controller’s Search method (which accepts a string search term).
  2. Then we perform a simple check to assert that the FindProducts method was called on IProductRepository.

Finally, we want to check what gets sent back to the view. For now we’ll simply check the name of the view being returned (and that the result is a view).

  1. It should_return_the_list_of_products_to_the_user =
  2.     () => _result.is_a_view_and().ViewName.ShouldEqual("SearchResults");

As I’ve gone along here I’ve implemented just enough to have the test compile.

IProductRepository
  1. public interface IProductRepository
  2. {
  3.     IList<Product> FindProducts(string searchTerm);
  4. }

ProductController
  1. public ActionResult Search(string searchTerm)
  2. {
  3.     throw new NotImplementedException();
  4. }

At this point we want to make the test pass so let’s do that.

  1. readonly IProductRepository _productRepository;
  2.  
  3. public ProductController(IProductRepository productRepository)
  4. {
  5.     _productRepository = productRepository;
  6. }
  7.  
  8. public ActionResult Search(string searchTerm)
  9. {
  10.     return View("SearchResults", _productRepository.FindProducts(searchTerm));
  11. }

Now our MSpec tests will pass.

When we run the tests, AutoMocker will generate a mocked version of our IProductRepository which our test uses to assert which methods were called. By letting AutoMocker generate our mocks we cut down on a lot of boilerplate code but don’t really loose anything in terms of flexibility and control (we can manually add mocks if we don’t want AutoMocker handling them for us).

Stubs

Now lets say we want to check that the list of products retrieved from the repository is actually sent back with the view.

For this, we can set up the automocked IProductRepository to return a specific list of products which we can then check are sent back to the user.

Lets start by telling automocker to stub the Search method and return a new list of products.

  1. Establish context =
  2.     () =>
  3.         {
  4.             _products = new List<Product>();
  5.             _productRepository.Stub(x => x.FindProducts("test")).Return(_products);
  6.         };

  1. We’ve added an Establish Context to our test (this will be used in conjunction with the one in our base class)
  2. We’ve said that when the FindProducts method is called with the parameter “test” then a new generic list of Product will be returned

Now we’ll change the implementation of the spec to check that this same list of products is returned with the view. Whilst we’re at it we’ll move the test for the view’s name into it’s own specification.

  1. It should_return_the_list_of_products_to_the_user =
  2.     () => _result.is_a_view_and().ViewData.Model.ShouldEqual(_products);
  3.  
  4. It should_return_the_search_results_page_to_the_user =
  5.     () => _result.is_a_view_and().ViewName.ShouldEqual("SearchResults");

Putting it all together

So finally, our completed test implementation now looks like this…

  1. [Subject("Product Search")]
  2. public class when_asked_for_products_matching_search_term : concern_for_product_controller
  3. {
  4.     static List<Product> _products;
  5.     static ActionResult _result;
  6.  
  7.     Establish context =
  8.         () =>
  9.             {
  10.                 _products = new List<Product>();
  11.                 _productRepository.Stub(x => x.FindProducts("test")).Return(_products);
  12.             };
  13.  
  14.     Because of =
  15.         () => { _result = _controller.Search("test"); };
  16.  
  17.     It should_retrieve_a_list_of_products_with_titles_containing_the_search_term =
  18.         () => _productRepository.AssertWasCalled(x => x.FindProducts("test"));
  19.  
  20.     It should_return_the_list_of_products_to_the_user =
  21.         () => _result.is_a_view_and().ViewData.Model.ShouldEqual(_products);
  22.  
  23.     It should_return_the_search_results_page_to_the_user =
  24.         () => _result.is_a_view_and().ViewName.ShouldEqual("SearchResults");
  25. }

Hopefully these posts have demonstrated how well MSpec and Rhino AutoMocker can work together. The key is that the tests are easy to maintain and clearly state the criteria by which to judge whether the code under test is valid.

These criteria are stated as human understandable specifications which can be agreed with your users before writing any code. RhinoAutoMocker makes mocking and stubbing very straightforward and helps you to defer making decisions until the last responsible moment.

You will find the source code for this sample project on GitHub.

Join the Practical ASP.NET Newsletter

Ship better Blazor apps, faster. One practical tip every Tuesday.

I respect your email privacy. Unsubscribe with one click.

    Next up

    BDD with MSpec and Rhino Auto Mocks (part 2)
    BDD with MSpec and Rhino Auto Mocks (part 1)