Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TDD'ing MVC Controllers to drive design

I am starting out on a new project (well, restarting an existing one), and trying to adopt TDD (for the nth time) for all the benefits that it should bring.

I believe that TDD will result in my tests driving me to write only the code that I need to write, but it will drive me to write the code that I NEED and not leave some out.

This is where my current state of uncertainty comes in.

Consider the story:

"A user must be able to add a widget, doing so they are taken to view the details of the newly added widget."

OK, so working from the UI (as that's where a user will add their widget from, and not using Visual Studio and a set of assemblies that I write)... I begin with the following test, writing the very minimal so that the test passes.

So I started out with the controller throwing a NotImplementedException, then returning a View()... the following was the first point that I had written the fewest lines I could to make the test pass.

[TestFixture]
public class WidgetControllerTester
{

    [Test]
    public void Create_IfBusinessModelIsValid_ReturnRedirectToRouteResultToDetailsAction()
    {
    // Arrange
        var currentUser = new User
                              {
                                  DisplayName = "Fred",
                                  Email = "[email protected]",
                                  Password = "pass",
                                  Status = UserStatus.Active
                              };
        var model = new WidgetModel();
        var controller = new WidgetController();
        // Act
        var actionResult = controller.Create(currentUser, model);
        // Assert
        actionResult.AssertActionRedirect().ToAction("Details");
    }
}

public class WidgetModel
{
}

public class WidgetController: Controller
{
    public ActionResult Create()
    {
        return View("Create");
    }

    [HttpPost]
    public ActionResult Create(User currentUser, Widget model)
    {
        return RedirectToAction("Details");
    }
}

Now I realise that additional tests for invalid models and checking of model state will evolve from additional stories.

However I can't see a clear path of how I would leverage additional tests to drive further code within the controller.

For instance, I know at some point I will want to make a WidgetService call from the Create action. Am I missing something obvious (not being able to see the wood for the trees kind of stuff) how I can progress the controller code with additional tests?

Talking about the WidgetService, I expect that I will write a WidgetServiceTester and for th e time being references within the controller will most likely be mocked.

Some thoughts I have had...

  • Create a new test called Create_IfModelIsValid_WidgetIsAddedToRepository, but how does this clearly lead onto service calls in the controller action?
  • I need to write a more detailed story stating that the model needs to be inserted into the repository/database, etc?
  • Am I muddling up elements of TDD and XP?

Thanks for reading, I would appreciate any feedback and insights to the best practice for progressing.

Joe.

EDIT 27 Feb 2010

I found the following article Iteration #6 – Use Test-Driven Development (on asp.net) (http://www.asp.net/%28S%28ywiyuluxr3qb2dfva1z5lgeg%29%29/learn/mvc/tutorial-31-cs.aspx) which demonstrates the sort of thing I was after, however they seem to consider addition of repository/service to the controller as re-factoring... I personally don't agree, am I wrong? :)

I'm going to think about writing a test that checks the ViewData of the Details action and update this question once I have.

like image 882
Joe Avatar asked Feb 26 '10 23:02

Joe


1 Answers

Joe. I feel a lot of the same uncertainty, sometimes. But I also think that most of what you're missing is those up-front stories. First, you should decompose your story just a tad to create the actual developer requirements. Here's what I mean:

"A user must be able to add a widget, doing so they are taken to view the details of the newly added widget."

Ok, so to me, that breaks out questions as follows, which can help you think of tests:

"A user" -- where did the user come from? How are they logged in? (if you're using the default AccountController and tests, then this is already there, if not, you'll want tests for getting the login form, logging in, validating both successful and failed logins, etc)

"add a widget" -- to what (I don't mean implementation, I just am pointing out that this implies that you're either going to hit a repository or a service, unless 'add' just means add it to the running instance and you don't need persistence)? does the widget have to be valid at this point, or can invalid widdgets be saved and made valid later? This implies to me tests that a repository or service has a method hit (save(), insert(), add(), whatever (not the internals of the method, until you get to testing your service/repo, just that the controller does its job by calling it), check what happens on a valid/invalid widget (you need to expand your story a bit or add a story to cover what should happen on valid/invalid widgets)

"doing so they are taken to view the newly added widget's details" -- reworded slightly, but basically what you said. Always? or only on success? Is this view editable or read-only (i.e. Edit action or Details action)? Is there a message to the user that tells them that they've been successful, or should they infer from the fact that they're viewing their widget that they were successful? This should drive tests that do things like check properties on the returned actionresult and check the values stored in TempData (status message) as well as checking what happens in both paths (success or failure).

that's just a quick shot, but basically that's the thought process. you can also do the same w/ other stories, and for that matter generate new stories to cover more application behavior.

A couple thoughts on your design going forward.

Your next test should look at what I wrote above first, which is the fact that the controller's create POST action should 1) receive needed data (your params), 2) call that service/repository to "add" the widget, 3) possibly do something if that add fails (this is in your design; I've gotten to where my controllers assume all will go well and I handle failures through attributes, but thats' a personal design decision), 4) redirect to details.

So, your next test would be using a mock (I prefer the moq library on google code, but whatever you have will work). You'll need some interface to describe the service your controller will call, and you pass in a mocked implementation of that to your controller under test in order to ensure that it's calling the correct method. In Moq, that'd look something like this:

[Test] 
public void Create_CallsRepository() 
{ 
    // Arrange 
    var currentUser = new User 
                          { 
                              DisplayName = "Fred", 
                              Email = "[email protected]", 
                              Password = "pass", 
                              Status = UserStatus.Active 
                          }; 
    var model = new WidgetModel(); 
    var mockService = new Mock<IService<WidgetModel>();
    mockService.Setup(s=>s.Add(model)); //.Returns(whatever) if it returns something
    var controller = new WidgetController(mockService.Object); 

    // Act 
    var actionResult = controller.Create(currentUser, model); 

    // Assert 
    mockService.Verify(s=>s.Add(model)); 
} 

That makes some design assumptions, of course, but the tension of how to write your tests vs how your objects should be called / handle things is part of what makes TDD so valuable.

like image 161
Paul Avatar answered Nov 15 '22 07:11

Paul