Site Meter
 
 

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.

21 Responses to Scaffolding Actions and Unit Tests with MvcScaffolding

  1. I like the direction this is heading in. T4 code generation in crud scenarios is already immensely productive.

  2. 3P

    I don’t see it helps me a lot. Copy/Paste test is a lot faster then scaffolding tests. Creating a snippet would be even faster.

    The variant that generates controller action, view, viemodel and tests would save most time and I could think of using it. But I think that misses the point when it comes to TDD. You first write test and it tells You what action methods, views, viewmodels You need to add.

  3. Steve Gentile

    Steve,

    Simply brilliant! Glad to see this project moving forward and growing.

    Question are you or do you plan on exposing the templates your using for customization?

    Thanks again – you’re always putting out some great stuff!

  4. Stephen

    I really like this idea of being able to quickly fire off commands in the package manager console rather than having to use the UI all the time.

    One question: does MvcScaffolding need to have a dependency on EntityFramework? The example above where you add an action method and a view doesn’t need it. I am thinking I would still like to use this on projects not using EntityFramework (although I can just ignore the reference to the dll it might confuse people).

  5. I like this project a lot in general, though when I tried uninstalling/reinstalling this time I got the following error:

    Install-Package : The operation has timed out
    At line:1 char:16
    + install-package <<<< mvcscaffolding
    + CategoryInfo : NotSpecified: (:) [Install-Package], WebException
    + FullyQualifiedErrorId : NuGetCmdletUnhandledException,NuGet.Cmdlets.InstallPackageCmdlet

    Any ideas why that might be? I’m not having problems installing anything else from this box.

  6. Sohan

    Good work, Steve!

  7. Pingback: DotNetShoutout

  8. Thanigainathan

    Very nice article. But the power of Visual Studio is its IDE. Why should I go back to command line ?

  9. Jimble

    This is a smart move and something that I will be using. Hopefully I will be able to swap out the razor stuff for spark and move the location where the controllers are created.

    Currently I use Resharper templates but this may be a quicker approach.

    I have always enjoyed the way that RoR have these helper methods.

    Anyone who thinks that copying and pasting or using VS as just an IDE with a mouse pointy clicky needs to wake up.

  10. jose pulido

    I got this error
    Scaffold CustomTemplate MvcScaffolding.UnitTest TestClass
    g:\mvc3Test\packages\T4Scaffolding.0.9.9\tools\CustomTemplate\T4Scaffolding.CustomTemplate.ps1 : Could not find scaffol
    der ‘MvcScaffolding.UnitTest’
    At line:1 char:16
    + param($c, $a) . <<<< $c @a
    + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,T4Scaffolding.CustomTemplate.ps1

  11. Steve

    Jose – sorry, it was a typo in the post. I’ve just updated it. The command you want is:

    Scaffold CustomTemplate MvcScaffolding.ActionUnitTest TestClass

    Notice the full scaffolder name should be “MvcScaffolding.ActionUnitTest” not “MvcScaffolding.UnitTest”

  12. Is there any way to use the scaffolding with areas? I have a model in an area, but the views and controllers are always created in the projects default view folder and not in the areas views folder.

    Good work on the project btw!

    Thanks
    Simon

  13. I have answered my own question. Sorry for not ready your other posts!

    Very simple and clear, using -Area
    worked first time

    Thanks
    Simon

  14. Thanks for the post. I have been using the MvcScaffolding for a while now and love it.

    Will the Scaffolder then with the Set-DefaultScaffolder Action MvcScaffolding.ActionWithUnitTest generate Unit test stubs for all actions when Scaffold Controller MyModel -Repository is used?

    Thanks
    Johan

  15. Rodrigo

    Hi Steve! Im trying to use the CustomTemplate feature to customize my RazorView _CreateOrEdit. But, after reopening the Visual Studio 2010 (with SP1), when I attempt to build the application, I got a lot of build errors from the _CreateOrEdit..cs.T4 file.
    Am I missing something?

    Thanks

  16. Steve

    Johan – Glad to hear it! Actually, the “controller” scaffolder has its own complete controller templates, so it doesn’t use the “action” scaffolder internally. So no, doing that wouldn’t cause unit test stubs to be generated for all the controller actions. If you did want that to happen, you could write custom scaffolder logic as I’ll show in the next few blog posts.

    Rodrigo – My guess is that you (or some tool you have installed) have set the t4 file’s “Build Action” (see the Properties pane in Visual Studio) to “Compile”. It shouldn’t be set to that – it should be set to “None”. Note that MvcScaffolding doesn’t change the build action like that , but some badly-behaved Visual Studio extensions for T4 highlighting do.

  17. gb

    Hi,
    I have just bought your book asp.net mvc3 and it’s good.
    I like your scaffolding posts.very good.
    what about scaffolding unitTests using moq and chance of a quick example?
    thanks

  18. JA

    Really amazing stuff. Would love to see how to add a Service Layer template to this collection, especially one which creates ViewModels for the Entity which the service returns. And possibly implements UnitOfWork too.
    Cheers.

  19. This is very good work! I really have benefited from not only what you have shared here, but your MVC books.

    If you were in here Africa I’d buy you a goat. ;)

  20. Matthew Moody

    I ran into an issue when running the following command:
    PM> Scaffold UnitTest Home ContactMe
    Added unit test class at HomeControllerTest.cs
    Get-ProjectType : Ambiguous type name ‘HomeControllerTest’. Try specifying the fully-qualified type name, including namespace.
    At C:\workspaces\SoccorSite\packages\MvcScaffolding.1.0.6\tools\ActionUnitTest\MvcScaffolding.ActionUnitTest.ps1:37 char:29
    + $testClass = Get-ProjectType <<<< $testClassName -Project $unitTestProject.Name
    + CategoryInfo : NotSpecified: (:) [Get-ProjectType], InvalidOperationException
    + FullyQualifiedErrorId : T4Scaffolding.Cmdlets.GetProjectTypeCmdlet

    Add-ClassMemberViaTemplate : Cannot bind argument to parameter 'CodeClass' because it is null.
    At C:\workspaces\SoccorSite\packages\MvcScaffolding.1.0.6\tools\ActionUnitTest\MvcScaffolding.ActionUnitTest.ps1:39 char:60
    + Add-ClassMemberViaTemplate -Name $testMethodName -CodeClass <<<< $testClass -Template TestMethod -Model @{
    + CategoryInfo : InvalidData: (:) [Add-ClassMemberViaTemplate], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,T4Scaffolding.Cmdlets.AddClassMemberViaTemplateCmdlet

    It is apparently confused as to the proper namespace/folder struction for MVC test projects.

    I admit I'm not a PS expert but I fixed it for myself by modifying line #28 of MvcScaffolding.ActionUnitTest.ps1 to
    $testClassNamespace = $unitTestProject.Properties.Item("DefaultNamespace").Value + ".Controllers"

    It's admittedly too simple, I'm sure there is a better way to do this.

    Great project.

  21. Nathan

    So good! Thanks for your hard work.