Failure is inevitable

It’s been a long time coming, but I finally shipped version 1.0 of SpecsFor.Mvc last week. There’s a slew of features in this release. Enough, in fact, for a series of blog posts. Hence this post! This is the first of many covering what you can do with SpecsFor.Mvc 1.0. Read on, and I’ll show you everything that’s in the box!

Motivation and History

This is a project that’s been brewing in the back of my mind since the ASP.NET MVC 1.0 days, when Jimmy Bogard presented his approach to integration testing to the C4MVC user group. Version 1.0 of SpecsFor.Mvc was driven from actual use on a jQuery Mobile application I wrote (more on that in a future post perhaps), and its feature set grew to meet my testing needs. This means that version 1.0 shipped with much more than I had originally planned. You can test everything from client-side script to E-mail sending with SpecsFor.Mvc, all with a strongly-typed, unit-test-like syntax. It can apply Web.config transforms prior to starting tests, there are hooks you can use to load test data into the app, and you can globally handle authentication without having to specify credentials in each spec. There’s a lot to cover, but first, let’s look at some of the challenges you would typically face in creating automated acceptance tests.

Challenges of Automated Acceptance Tests

One of the great benefits of unit testing is that it gives you the confidence to change your code. The idea is that if a change breaks something, a test will fail, and you’ll be able to correct the code long before it creeps into production. Good tests don’t get in the way of making changes to your code, they empower you to make changes by giving you confidence that everything still works as intended.

Unfortunately, one of the big challenges with any sort of test involving UI automation is creating tests that don’t become a barrier to making changes. Take a look at this example using Watin taken from a CodePlex article:

[Test]
public void CheckIfNicknameIsNotUsed()
{
// create a new Internet Explorer instance
// pointing to the ASP.NET Development Server
using (IE ie = new IE("http://localhost:8080/Default.aspx"))
{
// Maximize the IE window in order to view test
ie.ShowWindow(NativeMethods.WindowShowStyle.Maximize);
// search for txtNickName and type "gsus" in it
ie.TextField(Find.ById("txtNickName")).TypeText("gsus");
// fire the click event of the button
ie.Button(Find.ById("btnCheck")).Click();
// parse the response in order to fail the test or not
Assert.AreEqual(true, ie.ContainsText("The nickname is not used"));
}
}

Notice the liberal use of magic strings in this test. What happens if you refactor the underlying code? You’ll have to manually update this test because it is in no way strongly-tied to the application. That means this test is not empowering you to make changes, it’s actually introducing resistance to making changes. Friction, also referred to as rigid code, in an application is one of the signs of Bad Code.

Another challenge to crafting automated acceptance tests is the learning curve. Watin, Selenium, etc. all require you to learn their low-level API, and you have to worry about concerns that you typically don’t have to think about when performing unit tests, such as disposing of the browser, loading up seed data, and indeed even where to point the browser at for testing purposes.

SpecsFor.Mvc addresses all of those challenges, all by just installing a simple NuGet package.

Installing and Getting Started

SpecsFor.Mvc is an independent library for automated acceptance tests, but it plays so nicely with the core SpecsFor framework that you would be crazy not to use the two together, and that’s exactly what we’ll be doing in this series of posts. Let’s assume you already have an ASP.NET MVC application that you want to create integrated acceptance tests for. To get started with SpecsFor.Mvc, create a class library to serve as your test project, then install the SpecsFor and SpecsFor.Mvc NuGet packages:

To simplify things further, you may want to install the Resharper or Visual Studio code snippets for SpecsFor, which are included in the SpecsFor package. They provide snippets and templates for creating test fixtures, creating specs, etc.

SpecsFor.Mvc includes a test helper object called MvcWebApplication. This is the class you will create your specs around, like so:

Before we can start writing specs though, we need to tell SpecsFor.Mvc a little bit about the project its going to test.

Configuring SpecsFor.Mvc

SpecsFor.Mvc uses a fluent DSL (Domain-Specific Language) for configuration. You can build up multiple SpecsForMvcConfiguration objects and compose them together, or you can build up a single configuration instance. This configuration must be built up and passed to SpecsForIntegrationHost prior to any of your specifications executing. If you are using NUnit, you can utilize a SetUpFixture to perform this configuration. If you are using MS-Test, you can use an AssemblyInitialize method. Let’s look at an annotated example using NUnit:

//An NUnit SetUpFixture is executed before
//any tests in the fixture's namespace or
//subnamespaces. Usually you would put
//this in the root of your test project.
[SetUpFixture]
public class AssemblyStartup
{
private SpecsForIntegrationHost _host;
[SetUp]
public void SetupTestRun()
{
var config = new SpecsForMvcConfig();
//SpecsFor.Mvc can spin up an instance of IIS Express to host your app
//while the specs are executing.
config.UseIISExpress()
//To do that, it needs to know the name of the project to test...
.With(Project.Named("SpecsFor.Mvc.Demo"))
//And optionally, it can apply Web.config transformations if you want
//it to.
.ApplyWebConfigTransformForConfig("Test");
//In order to leverage the strongly-typed helpers in SpecsFor.Mvc,
//you need to tell it about your routes. Here we are just calling
//the infrastructure class from our MVC app that builds the RouteTable.
config.BuildRoutesUsing(r => MvcApplication.RegisterRoutes(r));
//SpecsFor.Mvc can use either Internet Explorer or Firefox. Support
//for Chrome is planned for a future release.
config.UseBrowser(BrowserDriver.InternetExplorer);
//Does your application send E-mails? Well, SpecsFor.Mvc can intercept
//those while your specifications are executing, enabling you to write
//tests against the contents of sent messages.
config.InterceptEmailMessagesOnPort(13565);
//The host takes our configuration and performs all the magic. We
//need to keep a reference to it so we can shut it down after all
//the specifications have executed.
_host = new SpecsForIntegrationHost(config);
_host.Start();
}
//The TearDown method will be called once all the specs have executed.
//All we need to do is stop the integration host, and it will take
//care of shutting down the browser, IIS Express, etc.
[TearDown]
public void TearDownTestRun()
{
_host.Shutdown();
}
}

There’s not much config here, but we’re enabling SpecsFor.Mvc to do a lot. We’re telling it which project we are creating tests for, which Web.config transformation to apply, what the routes are for our application, which browser to use, and that we want it to intercept E-mail messages. Once we start the integration host, it will take care of doing all the magic for us, we just need to write our specs.

A Simple Test Case

Now that SpecsFor.Mvc is configured, we can create a test. Let’s look at a simple example of navigating to a form, filling in data, submitting it, and verifying the results. First, let’s navigate to the form:

Notice that we’re using strongly-typed lambda expressions to navigate. This makes our test refactor-friendly. We can rename the action method using Resharper (or Visual Studio’s refactoring tools if you just hate yourself), and our test will be updated as well.

Next, let’s fill out the form on the page. Assuming we’re generating strongly-typed views (AND YOU SHOULD BE!), you can again use strongly-typed lambda expressions to fill out the form:

There’s still more though. What about applications that send E-mail messages? SpecsFor.Mvc can help you test those behaviors as well.

Checking E-mail

SpecsFor.Mvc includes a light-weight SMTP server that can intercept E-mail messages. In order to use this feature, you have to also take advantage of another SpecsFor.Mvc feature: Web.config transformations. Create a ‘Test’ build configuration for your MVC project, and create a Web.Test.config tranform that will change your SMTP server to point to a known port on the local machine. Here’s an example:

SpecsFor.Mvc exposes the actual E-mail message that was received by the embedded SMTP server, so you can verify the recipients, subject, body, attachments, etc.

Just Scratching the Surface

This post has only touched on some of the things that SpecsFor.Mvc can do for you, and only at a very high level. In the next post in this series, we’ll start diving deep into the various features and how you can leverage them to create automated acceptance tests that empower you and your team to make changes and drive the development of new features. In the meantime, check out the code on Github, install the NuGet packages, and let me know what you think!

About Matt Honeycutt...

Matt Honeycutt is a software architect specializing in ASP.NET web applications, particularly ASP.NET MVC. He has over a decade of experience in building (and testing!) web applications.
He’s an avid practitioner of Test-Driven Development, creating both the SpecsFor and SpecsFor.Mvc frameworks.

He's also an author for Pluralsight,
where he publishes courses on everything from web applications to testing!