I’ve recently spent some time looking into different frameworks for doing integration testing for ASP.NET MVC projects. One that caught my eye almost immediately was SpecsFor.Mvc. Unlike other solutions I found for writing integration tests, SpecsFor.Mvc lets you write tests in a fashion that is very similar to writing unit tests, using strongly-typed access to your application’s data without having to script or dig into the DOM.
Some nice things that SpecsFor.Mvc provides out-of-the-box:
- Hosts your ASP.NET MVC project, building a specified configuration of your project and then hosting it automatically under an instance of IIS Express
- Provides strongly-typed methods for navigating to controllers and actions, checking route results, and filling out and submitting forms
- Provides access to validation data, including access to the validation summary as well as the validity of each property in your view’s model
SpecsFor.Mvc uses Selenium WebDriver internally in order to drive the browser. You can still access the Selenium IWebDriver interface any time you need to dig further into your page.
Let’s take a look at these things in practice by writing a few hypothetical tests written against the stock ASP.NET MVC Internet Application.
Starting the Project
To get started, create a new ASP.NET MVC 4 project in Visual Studio.
Select the Internet Application project template, check the option to create a unit test project and click OK.
Once both the ASP.NET MVC project and the unit test project have been created, right-click the References folder under your Tests project and click Manage Nuget Packages.
Under Online, search for and install the official SpecsFor.Mvc Nuget package.
Initializing the Hosting Environment
The next thing that we need to add to the Tests project is some code that will initialize the IIS Express hosting environment using the classes provided by SpecsFor.Mvc. To do this, create a new class called MvcAppConfig with the following contents (adjust the namespace as needed):
private static SpecsForIntegrationHost integrationHost;
public static void MyAssemblyInitialize(TestContext testContext)
var config = new SpecsForMvcConfig();
config.BuildRoutesUsing(r => RouteConfig.RegisterRoutes(r));
integrationHost = new SpecsForIntegrationHost(config);
public static void MyAssemblyCleanup()
The class is marked as a TestClass even though there are no explicit methods to test. This is so that the MyAssemblyInitialize() and MyAssemblyCleanup() methods run. In order for the AssemblyInitialize and AssemblyCleanup attributes to work the class must be marked with the TestClass attribute. With this code in place, MyAssemblyInitialize() will run once before all of the test methods in the project and MyAssemblyCleanup() will run after they all complete.
The code found in MyAssemblyInitialize() is fairly straight-forward given the clarity of the SpecsFor.Mvc API. A new SpecsForMvcConfig instance is created and set to use IIS Express with a given project name and configuration name. Next, a call to BuildRoutesUsing is made in order to register the various controllers and actions with SpecsFor.Mvc. Finally, the browser is specified and the configuration is used to start a new instance of the SpecsForIntegrationHost.
The MyAssemblyCleanup() method, paired with the AssemblyCleanup attribute, is used to shut down the integration host after all the tests have completed.
Initializing the Browser
Now that we have code in place to host the ASP.NET MVC site before any tests are run, we need some code in place to create an instance of our MVC application in a browser. Right-click the Tests project and add a new Unit Test.
Add the following code to the top of your new UnitTest1 class, before the TestMethod1 declaration:
private static MvcWebApp app;
public static void MyClassInitialize(TestContext testContext)
app = new MvcWebApp();
This will require adding a using statement for SpecsFor.Mvc.
This new method, MyClassInitialize() will run before all of the tests in the new UnitTest1 class. It will create a new instance of the MvcWebApp class which will launch the browser with your application loaded.
If you go ahead and run the tests for UnitTest1 now you’ll see that two console windows are opened, one for IIS Express hosting the ASP.NET application and one for the Selenium WebDriver that is driving your application. In addition, after the Selenium WebDriver console window is opened, the browser specified in the MvcAppConfig class will be launched.
Note that you may get a prompt from Windows Firewall that you’ll need to allow.
Because we haven’t actually written any tests yet, all these windows will close after they are opened, but this demonstrates that these few lines of code used to bootstrap the environment are working.
Now that all the setup work is done, let’s see what some actual integration tests look like using SpecsFor.Mvc. The first test will ensure that, if a user tries to navigate to the /account/manage route of the ASP.NET MVC application without logging in, they will be redirected to the login screen.
public void AccountManage_WithoutSession_RedirectsToLogin()
AccountController.ManageMessageId? messageId = null;
app.NavigateTo<AccountController>(c => c.Manage(messageId));
const string returnUrl = "%2fAccount%2fManage";
app.Route.ShouldMapTo<AccountController>(c => c.Login(returnUrl));
This test will require adding two new items to the using statements: YourProjectName.Controllers and MvcContrib.TestHelper (MvcContrib.TestHelper is needed for the call to ShouldMapTo).
And that’s it for the first integration test. I love it. It’s clear, concise, and (aside from the return URL path) it’s strongly typed. The call to NavigateTo will navigate to the URL corresponding to the AccountController and the Manage action, specified in the lambda expression. The call to ShouldMapTo will ensure that the resulting route corresponds to the AccountController and Login action (with the proper ReturnUrl parameter).
Let’s add two more tests to illustrate a few more examples using SpecsFor.Mvc:
public void Login_InvalidInput_TriggersValidation()
app.NavigateTo<AccountController>(c => c.Login(string.Empty));
.Field(f => f.UserName).SetValueTo(string.Empty)
.Field(f => f.Password).SetValueTo(string.Empty)
.Field(f => f.UserName).ShouldBeInvalid();
.Field(f => f.Password).ShouldBeInvalid();
public void Login_InvalidCredentials_TriggersValidation()
app.NavigateTo<AccountController>(c => c.Login(string.Empty));
.Field(f => f.UserName).SetValueTo(Guid.NewGuid().ToString())
.Field(f => f.Password).SetValueTo(Guid.NewGuid().ToString())
These tests will require adding a using statement for YourProjectName.Models so that the LoginModel class can be accessed.
Again, looking at the code, I love the simplicity and clarity in the SpecsFor.Mvc tests. I can use NavigateTo to navigate to my controller and action, and then use FindFormFor to access my view’s model. Finally I can submit the form with easy access to the resulting validation data.
Unfortunately, if you try to run these new tests right now they will fail. The reason is that the SpecsFor.Mvc initialization code compiles and deploys a fresh copy of the ASP.NET MVC project to a TestSite folder within the Debug folder. The App_Data folder contents are not included in the ASP.NET MVC Visual Studio project. So, the database files are not deployed to the TestSite folder and the site itself will YSOD if you try to do anything requiring the database.
To fix this, right-click the App_Data folder in your main MVC project and click Add>Existing Item.
Then, add the two files found in your physical App_Data folder to the project (you’ll need to run the MVC site and access the database once manually).
After adding the MDF and LDF files to the project you should be able to run all of the authentication integration tests without error.
The Big But
Now this all sounds great, but…
At the time I’m writing this, SpecsFor.Mvc tests run great under third-party test runners such as TestDriven.Net and CodeRush. However, the tests don’t run under Visual Studio’s MSTest runner. Trying to run the tests using Visual Studio’s built in test runner will result in a “Build failed” error. The author of SpecsFor.Mvc has reproduced the issue and is hoping to have it fixed within a couple of days.
UPDATE: This issue has since been resolved by Matt and is no longer a problem in version 2.4.0. No more buts!