Apr 19 2012

Using SpecsFor.Mvc - Reading Data

Category: SpecsFor | TestingMatt @ 16:57

So far in the “Using SpecsFor.Mvc" series, I’ve shown you how to navigate using the strongly-typed API and how to fill out and submit forms.  In part three of my series, I’ll show you how to create automated acceptance tests for your ASP.NET MVC application that verify expected data is displayed on a page. 

The “Using SpecsFor.Mvc” Series

Our Configuration

Once again we’ll be using the SpecsFor.Demo application (available on Github) with a standard SpecsFor.Mvc configuration:

[SetUpFixture]
public class AssemblyStartup
{
    private SpecsForIntegrationHost _host;

    [SetUp]
    public void SetupTestRun()
    {
        var config = new SpecsForMvcConfig();
        config.UseIISExpress()
            .With(Project.Named("SpecsFor.Mvc.Demo"))
            .ApplyWebConfigTransformForConfig("Test");

        config.BuildRoutesUsing(r => MvcApplication.RegisterRoutes(r));
        config.UseBrowser(BrowserDriver.InternetExplorer);

        config.InterceptEmailMessagesOnPort(13565);

        _host = new SpecsForIntegrationHost(config);
        _host.Start();
    }

    [TearDown]
    public void TearDownTestRun()
    {
        _host.Shutdown();
    }
}

Check out part one if you want to know more about what this configuration is doing.

The Many Ways to Display Data in ASP.NET MVC

We covered in part two that there are many ways to build forms with ASP.NET MVC.  It’s also true that there are many ways to build display views.  You can emit values from ViewData:

<h2>About</h2>
<p>
     Today is @Html.ViewData["DayOfWeek"]
</p>

Or you can write values directly from a strongly-typed view model:

<h2>About</h2>
<p>
     Today is @Model.DayOfWeek
</p>

You can also use strongly-typed helpers:

<h2>About</h2>
<p>
     Today is @Html.DisplayFor(m => m.DayOfWeek)
</p>

The later approach is the preferred one, and it has several benefits.  First, unlike rendering values from ViewData or ViewBag, the strongly-typed helpers give you compile-time safety.  Second, the helpers are built on ASP.NET MVC’s templating system, giving you additional hooks to change how things are rendered across your application.  We’ll be leveraging these templates to create testable views.

Creating Testable Views

If you aren’t familiar with ASP.NET MVC’s display and editor templates, I strongly recommend you check out Brad Wilson’s posts on the subject.  We’ll be leveraging display templates here, and I’m going to assume you understand the basics.

The built-in templates included with ASP.NET MVC are actually implemented as static methods in C# and cannot be customized incrementally.  Here’s the string template, buried within the bowels of the System.Web.Mvc.Html namespace:

internal static class DefaultDisplayTemplates {

    //..snip..

    internal static string StringTemplate(HtmlHelper html) { 
        return html.Encode(html.ViewContext.ViewData.TemplateInfo.FormattedModelValue);
    }

    //..snip..

}

And here’s the output of this template:

<h2>About</h2>
<p>
     Today is Friday
</p>

Notice that there’s nothing about the output that makes it obvious that part of the result was pulled from the view model.  This is quite different from the editor templates.  They output identifiers based on the model:

<div class="editor-field">
    <input data-val="true" data-val-required="The User name field is required." id="UserName" name="UserName" type="text" value="">
    <span class="field-validation-valid" data-valmsg-for="UserName" data-valmsg-replace="true"></span>
</div>

In order for SpecsFor.Mvc to locate the output of our display view models, we need to tag the output in such a way that it can be identified.  That’s not as easy as it should be though.  Remember what I said about the display templates: they’re baked in to the System.Web.Mvc assembly.  Since the built-in templates aren’t extensible, it’s unfortunately necessary to replace them completely with templates that can be customized and extended.  I’ve created a NuGet package, MvcDisplayTemplates, that contains editable Razor versions of the built-in templates with only minor enhancements.  The main enhancement that’s relevant here is the addition of a shared layout that wraps the output of each template with a span.  Through its ID attribute, which is populated using the same logic that builds IDs for the built-in editor templates, the span element makes it very easy to determine where properties from our view model were rendered in the view:

<h2>About</h2>
<p>
     Today is <span id="DayOfWeek">Friday</span>
</p>

Go ahead and install the MvcDisplayTemplates into your web application (and not your test project) now.  There, you’re all set to create testable views!

Testing Display Views with SpecsFor.Mvc

Leveraging the test-friendly templates is easy: just render the output using HtmlHelper’s DisplayFor extension method as shown in the example above.  Testing the view’s output with SpecsFor.Mvc is then very similar to interacting with and submitting forms.  Navigate to the controller action, use the FindDisplayFor method to retrieve a FluentDisplay, then use the DisplayFor method to locate the elements for the various properties on your view model:

public class when_viewing_the_about_page : SpecsFor<MvcWebApp>
{
    protected override void When()
    {
        SUT.NavigateTo<HomeController>(c => c.About());
    }

    [Test]
    public void then_it_displays_the_current_day_of_the_week()
    {
        SUT.FindDisplayFor<AboutViewModel>()
            .DisplayFor(m => m.DayOfWeek).Text.ShouldEqual(DateTime.Today.DayOfWeek.ToString());
    }
}

This approach also works with nested display models:

public class when_viewing_the_about_page : SpecsFor<MvcWebApp>
{
    //..snip..

    [Test]
    public void then_it_knows_the_user_is_anonymous()
    {
        SUT.FindDisplayFor<AboutViewModel>()
            .DisplayFor(m => m.User.UserName).Text.ShouldEqual("Anonymous");
    }
}

As well as with collections:

public class when_viewing_the_about_page : SpecsFor<MvcWebApp>
{
    //..snip..

    [Test]
    public void then_it_displays_the_correct_business_days()
    {
        SUT.FindDisplayFor<AboutViewModel>()
            .DisplayFor(m => m.BusinessDays[0]).Text.ShouldEqual("Monday");

        SUT.FindDisplayFor<AboutViewModel>()
            .DisplayFor(m => m.BusinessDays[3]).Text.ShouldEqual("Saturday");
    }
}

As long as the output is wrapped in a span with an ID generated off of the model, SpecsFor.Mvc should be able to locate it.  The convention for naming is hardcoded in the current build of SpecsFor.Mvc and matches the approach System.Web.Mvc uses, but I plan to make that a customizable convention in the 2.0 release.

What’s Next?

Assuming you are generating views with the correct conventions (which is easy thanks to the MvcDisplayTemplates package on NuGet), SpecsFor.Mvc makes it very easy to test the output of your views.  Now you know how to submit data to your app as well as how to read it back out.  In the next post, I’ll show you how SpecsFor.Mvc helps you deal with authentication without adding overhead to your specs.  In future posts, I’ll show you how you can load seed data into your application as well as how you can use SpecsFor.Mvc to test JavaScript-heavy applications.  Stay tuned!

Tags:

blog comments powered by Disqus