Site Meter
 
 

Monthly Archives: March 2011

Scaffolding Actions and Unit Tests with MvcScaffolding

This blog post is part of a series about the MvcScaffolding NuGet package:

  1. Introduction: Scaffold your ASP.NET MVC 3 project with the MvcScaffolding package
  2. Standard usage: Typical use cases and options
  3. One-to-Many Relationships
  4. This post: Scaffolding Actions and Unit Tests
  5. Overriding the T4 templates
  6. Creating custom scaffolders
  7. Scaffolding custom collections of files

So, if you’ve read any of my previous posts about MvcScaffolding, you’ll know that you can use it for “CRUD” scenarios: i.e., making create, read, update, delete pages so that users can enter and edit data. Scaffolding and CRUD go together so often that people get the impression that scaffolding is only about CRUD. Well, it’s not. You can do a lot more with scaffolding.

In this post I’ll show you how to use MvcScaffolding to quickly add action methods to controllers (along with their views and view models), and to scaffold unit test stubs for them.

Hang on,” you might be thinking, “I can already add action methods and unit tests. I just type them into Visual Studio.” Yes, yes, of course you can. But this way is faster, and lets you set up and share your own templates for them. Like snippets, but with more command-line smarts.

Installing or Updating MvcScaffolding

First, open an ASP.NET MVC 3 project, open the Package Manager Console, and then install the MvcScaffolding package, being sure to get version 0.9.8 or later:

Install-Package MvcScaffolding

If your project already has a version of MvcScaffolding older than 0.9.8, update it like this:

Update-Package MvcScaffolding

Note: If you’re updating from an older version, this will prompt you to restart Visual Studio before continuing the installation. Obey it, or badness will happen! So, after restarting VS, run “Install-Package MvcScaffolding” again. This time it won’t prompt you to restart VS.

Scaffolding Actions

To add a ContactMe action to your HomeController, run the following scaffolding command:

Scaffold Action Home ContactMe

What will happen? HomeController will have a new method:

public class HomeController : Controller
{
    // ... existing code not affected ...
 
    public ViewResult ContactMe()
    {
        return View();
    }
}

… and there’ll be a view for ContactMe at /Views/Home/ContactMe.cshtml.

Excellent. That was slightly quicker than adding the action and view by hand. But I almost always want to pass a view model from my actions to their views, so there’s a clean, strongly-typed way of transferring action-specific data. No problem. There’s a switch for that.

Delete the ContactMe action and view (or use the –Force) switch, and rescaffold as follows:

Scaffold Action Home ContactMe –WithViewModel

What happens?

SNAGHTML3efc96dd

This time it’s added an action that uses a view model:

public class HomeController : Controller
{
    // ... existing code not affected ...
 
    public ViewResult ContactMe()
    {
        return View(new ContactMeViewModel {
            // Populate properties here
        });
    }
}

… and created a view model at /Models/ContactMeViewModel.cs:

public class ContactMeViewModel
{
    // Add properties here
}

… and made the view, /Views/Home/ContactMe.cshtml strongly typed for the ContactMeViewModel class.

Or, if you prefer the view model class to be called ContactInfo, use the –ViewModel parameter instead of the –WithViewModel switch:

Scaffold Action Home ContactMe -ViewModel ContactInfo

If there’s already a ContactInfo class, it will use it (wherever it is), or if not it will create a new one in /Models.

Now you can go ahead and add suitable properties to the view model class and reference them in both the action and the view. In total, this saves a few more seconds compared to creating all those files by hand.

Scaffolding Post-handling Actions

OK, so you’ve created your basic action. Maybe you’ve added some properties to the view model, like this:

public class ContactInfo
{
    [Required] public string Name { get; set; }
    [Required] public string EmailAddress { get; set; }
}

… and edited the view, /Views/Home/ContactMe.cshtml to display a form:

@model ActionScaffoldingExample.Models.ContactInfo
@{ ViewBag.Title = "ContactMe"; }
 
<h2>Contact Me</h2>
<p>Give us your contact details, and we'll, well, contact you. Obviously.</p>
 
@using (Html.BeginForm()) {
    @Html.EditorForModel()
    <input type="submit" />
}

Next, you need some action to receive the posted data. You could create it by hand like normal. Or, you could scaffold it, like this:

Scaffold Action Home ContactMe -ViewModel ContactInfo -Post

This adds the following action method (without affecting your existing code):

[HttpPost, ActionName("ContactMe")]
public ActionResult ContactMePost(ContactInfo contactinfo)
{
    if (ModelState.IsValid) {
        return RedirectToAction("Index");
    } else {
        return View(contactinfo);
    }
}

You can go ahead and edit this to put in some code to send an email, save the data to a database, or whatever. But right away you’ve got the basics of handling form posts, slightly quicker than typing it out by hand.

Scaffolding Unit Test Stubs

If you’re writing unit tests, you probably need all the help you can get with making the process more streamlined. The benefits of unit testing are high, but so are the costs, so let’s try to reduce the costs a bit.

Do you already have a unit test project in your solution called “{YourMvcProjectName}.Test” or “{YourMvcProjectName}.Tests”? If not, you can add one by right-clicking your solution name in Solution Explorer, choosing Add->New Project, and choosing “Test Project” (use “Search Installed Templates” if you can’t find that). To follow the naming convention, if your main project is called MySuperApp, call the test project MySuperApp.Test or MySuperApp.Tests. Finally, add a reference from the new test project to System.Web.Mvc and to your main MVC application project.

Now you can run the following scaffolding command:

Scaffold UnitTest Home ContactMe

What will this do?

  • If you don’t already have a HomeControllerTest.cs class file in your test project, it will create one
  • Then it will add a unit test stub for your ContactMe action:
    [TestClass]
    public class HomeControllerTest
    {
        [TestMethod]
        public void ContactMeTest()
        {
            // Arrange
            var controller = new HomeController();
 
            // Act
            var viewResult = controller.ContactMe();
            var viewModel = viewResult.Model;
 
            // Assert
            // Assert.AreEqual("expected value", viewModel.SomeProperty);
            Assert.Inconclusive(); // Todo: Make assertions, then remove this line
        }
    }

Add some assertions, remove the “Assert.Inconclusive” line (which is there to make sure you don’t just leave the stub code as-is and think you’ve actually got a passing test) and you’re away.

If you will be writing assertions about property values on your view model class, you can optionally specify the view model class name. Either write:

Scaffold UnitTest Home ContactMe –WithViewModel

… to use the default view model name, or

Scaffold UnitTest Home ContactMe –ViewModel ContactInfo

… to specify a custom view model class name. If you do either of these, the unit test stub will be set up to require the ViewResult to have a Model value of the right type:

// Act
var viewResult = controller.ContactMe();
var viewModel = (ContactInfo)viewResult.Model;

Now you can easily add assertions based on this view model type.

Scaffolding Unit Test Stubs for Post-handling Actions

If you want a unit test to specify how your post-handling action should behave, just specify the post-handling action by name. For example,

Scaffold UnitTest Home ContactMePost

This will generate the following test stub:

[TestMethod]
public void ContactMePostTest()
{
    // Arrange
    var controller = new HomeController();
 
    // Act
    var actionResult = controller.ContactMePost(null);
 
    // Assert
    Assert.Inconclusive(); // Todo: Make assertions, then remove this line
}

If you want to write a test to specify how the action handles particular ContactInfo parameter values, construct a ContactInfo instance in the test and replace the “null” in “controller.ContactMePost(null)” with your instance variable. Of course, this will not test model binding (including validation that happens during model binding), as that’s a separate concern.

Notice that this time, the generated code doesn’t include anything to do with a ViewResult. Why is this? The template for test methods inspects the return type of your action method, and produces different code depending on that type.

  • If your method returns a ViewResult, the test will automatically extract the Model value (cast to your view model type, if given), so you can write assertions against it
  • If your method returns a RedirectToRouteResult, the test will automatically grab the RouteData values so you can write assertions about them
  • If your method returns a general ActionResult or something else, as in the last example, the test will just obtain the action result instance and it’s up to you what to do with it.

You can customize this logic by customizing the template if you want (details below).

And what if you don’t want to use MSTest, but would rather use NUnit or something else? That’s also a matter of customizing the templates.

Scaffolding Actions *with* Unit Tests in One Shot

If you want both the action and a test stub for it in a single command, try this:

Scaffold MvcScaffolding.ActionWithUnitTest Home SomethingElse -WithViewModel

This one command gives you an action called SomethingElse, a view model class called SomethingElseViewModel, a view (strongly-typed), a unit test class, and a unit test method stub (set up to work with the generated view model class). Surely that is saving you several seconds at least.

If you wanted to establish the standard that all scaffolded actions should have unit tests, write this:

Set-DefaultScaffolder Action MvcScaffolding.ActionWithUnitTest

Now, “Scaffold Action” will map to the MvcScaffolding.ActionWithUnitTest scaffolder, instead of mapping to MvcScaffolding.Action as it does by default.

Customizing the Templates

I’m about to write a whole post about customizing the templates, but in the meantime, here’s a sneak preview. Try any of the following:

Scaffold CustomTemplate MvcScaffolding.Action Action
Scaffold CustomTemplate MvcScaffolding.Action ActionPost
Scaffold CustomTemplate MvcScaffolding.Action ViewModel
Scaffold CustomTemplate MvcScaffolding.ActionUnitTest TestClass
Scaffold CustomTemplate MvcScaffolding.ActionUnitTest TestMethod

Then edit the T4 templates that appear in your project’s /CodeTemplates/Scaffolders folder.

Then run the scaffolding commands again (remember to pass –Force if you want to overwrite existing files).

See the difference.

To share your templates overrides with others on your team, simply commit them into source control.

Conclusion

For people who enjoy the power of the Package Manager Console command line, scaffolding actions and unit tests is a fast and customizable way of adding arbitrary code to your project.

In a future post, I’ll show how to create entirely new scaffolders of your own that apply custom PowerShell logic and can add any combination of files and class members, rather than just customizing T4 templates.