I've been playing with the new ASP.NET 3.5 Extensions preview (which includes a build of the new ASP.NET MVC stuff) and I was curious to see how well it would live up to the claims that is supports TDD.  I decided to start using a sample application from the Ruby On Rails world known as the 'Cookbook' application. It's real basic. You can list and create new recipes and you can categorize the recipes. Nothing fancy.

NOTE: This is based on a preview release of the ASP.NET MVC bits. I'm CERTAIN it will change before CTP, Beta, and release, so please take that into consideration.

NOTE: Also, this isn't a dissertation on how to best do TDD or DDD. I'm just going through the basics for demonstration purposes. If you don't like how I'm doing testing, PLEASE BLOG ABOUT IT AND LET ME KNOW :)  Help spread the Love And Knowledge. I can always learn more about DDD, TDD and testing in general.

Getting Started

The RoR example uses ActiveRecord for data access. I'm not that familiar with ActiveRecord, so I decided to use a DDD-like approach for data access. So, I'm going to use an IXYZRepository approach. I know I'm going to need a RecipeController to list, edit, and update recipes (and maybe search, delete, and do other things later, but let's not worry about that now). I know it's going to list recipes and categories, so I'm going to need a Repo for each of those.

Here's what my RecipeController stub looks like:

public class RecipeController : Controller
{
    private IRecipeRepository _recipeRepo;
    private ICategoryRepository _categoryRepo;

    // TODO: Remove this c'tor. Depdency inject this instead 
    // of hard-coding it (keeping it simple for demo purposes)
    public RecipeController()
        : this(new RecipeRepository(), new CategoryRepository())
    {
    }

    public RecipeController(
        IRecipeRepository recipeRepo, 
        ICategoryRepository categoryRepo)
    {
        _recipeRepo = recipeRepo;
        _categoryRepo = categoryRepo;
    }

    [ControllerAction]
    public void List()
    {
        throw new NotImplementedException();
    }
}

Note that I have not actually implemented anything yet. I just throw an exception to mark where the test should fail. Ok, now I can start with my unit tests. For my unit tests, I'm going to use the NUnit testing framework. I'm also going to use Rhino.Mocks in order to mock my dependency interfaces.  First, I'm going create my test fixture class and decorate it with the appropriate NUnit attributes. I'll need a SetUp method to setup my MockRepository and my mocked interface dependencies.

[TestFixture]
public class RecipeControllerFixture
{
    private MockRepository _mockRepo;
    private RecipeController _controller;
    private IRecipeRepository _recipeRepo;
    private ICategoryRepository _categoryRepo;
    private IViewFactory _viewFactory;
    private IView _view;

    [SetUp]
    public void SetUp()
    {
        _mockRepo = new MockRepository();
        _recipeRepo = _mockRepo.DynamicMock<IRecipeRepository>();
        _categoryRepo = _mockRepo.DynamicMock<ICategoryRepository>();
        _viewFactory = _mockRepo.DynamicMock<IViewFactory>();
        _view = _mockRepo.DynamicMock<IView>();

        _controller = new RecipeController(_recipeRepo, _categoryRepo);
        _controller.ViewFactory = _viewFactory;
    }
}

I've now created a RecipeController instance and provided for all its dependencies. It has three dependencies:

  1. An IReceipeRepository (c'tor injected)
  2. An ICategoryRepository (c'tor injected)
  3. An IViewFactory (property injected -- requirement of the MVC framework base class 'Controller' -- it'd be nicer if we had other options here, but this is still very good, IMHO)

The MVC framework also has its own dependency in that the ViewFactory MUST return a non-null instance of IView.  This is unfortunate as we can't have fully isolated unit tests (because now my RecipeController tests must necessarily be worried about what the MVC framework is doing with the IViewFactory -- which they shouldn't have to).  But this is easily resolved using Rhino.Mocks' DynamicMock functionality which does the appropriate thing in most circumstances, so we don't have to worry about satisfying all of the MVC framework's dependencies.

The First Test, The First Failure

We're going to test the List() action. The list action will be responsible for retrieving all the recipes and providing them to the view.  We'll expect that the List() action will call the IReceipeRepsitory and provide the results (whatever they may be) to the view using the MVC's RenderView method on the base Controller class.

Here's what our first test looks like:

[Test]
public void 
List_action_should_retrieve_all_recipes_and_pass_them_to_the__List__view() { List<Recipe> recipeList = new List<Recipe>(); using (_mockRepo.Record()) { Expect.Call(_recipeRepo.FindAll()).Return(recipeList); }
    using (_mockRepo.Playback())
    {
        _controller.List();
    }
}

Here we're setting up the expectation of a call to recipeRepo.FindAll() and it should return an IEnumerable<Recipe> list of recipes (in this case, List<Recipe> for ease of testing, the actual implementation may return something else that implements IEnumerable<Recipe>.  So let's give it a shot. We CTRL+SHIFT+B to compile, and then I use CTRL+T with ReSharper's test runner to start the test. And here's what I get:

Horray! Our first failure! We're now at the 'RED' part of 'RED, GREEN, REFACTOR' which is a mantra of TDD.  Why did it fail? Because we haven't implemented anything. It behaved like we Now, let's make the test pass. 

 

The Next Try

I'm going to change my List() method to actually call FindAll() on IRecipeRepository and then I'm going to compile and try again.  Here's the updated List() action method:

[ControllerAction]
public void List()
{
    var recipes = _recipeRepo.FindAll();
    RenderView("List", recipes);
}

Recompile, run the test, but ACK! I get a new, strange error that seems to have nothing to do with that I'm testing:

It turns out that the RenderView method expects the ViewFactory to do a few things. We'll need to set up some mock expectations for him too:

Expect.Call(_viewFactory.CreateView(null, null, null, null))
    .Constraints(
        Is.Anything(),
        Is.Equal("List"),
        Is.Anything(),
        Is.Same(recipeList))
    .Return(_view);

I need ensure that the view factory is set up right and returns a view during the test. I need to test that the correct values are going to get passed to the view from my controller. Unfortunately, I need to go through the RenderView code to do that.  I don't need to test all the arguments on the CreateView method, just two: 1.) Make sure that the correct view is getting loaded (i.e. "List" and not "Error" or "SomethingElse") and 2.) Make sure that the list returned from the IRecipeRepository is getting passed to the view like we expect.

 

Third Time's a Charm

Recompile, re-run tests...

SUCCESS! I have now tested that my controller behaves like I expect it to when a List() request comes in. I have established that I can test my controller without (much) framework wiring, without having to make web requests or hosting the ASP.NET pipeline.

Conclusion

I'm very encouraged by these advancements in the ASP.NET MVC framework. There are a few tweaks I hope are made to make testing even more frictionless, but if this is the worst we get, it is very good indeed!

kick it on DotNetKicks.com

Technorati Tags: ,