CodeProject
While many of the single components of an ASP.NET MVC application are easy to test (-drive) in an isolated manner (e.g. controllers), this can be hard for some others (like e.g. model binders) - and sometimes it just doesn't make much sense to test a single piece of code in isolation (e.g. for security-related issues). In such a case, some sort of integration testing has to be done, which in the context of ASP.NET MVC typically implies the usage of a browser automation framework like e.g. Selenium RC, WatiN, or the Lightweight Test Automation Framework. Such tools automate a real browser instance and require the tested web site to be hosted on a web server. Consequently, this way of testing needs quite a few resources and introduces its own set of problems and error possibilities...
When looking for a way to make these kinds of tests a bit more developer-friendly and less resource-demanding and error-prone, I came across this blog post: Integration Testing Your ASP.NET MVC Application. It introduces a framework for integration testing ASP.NET MVC applications without the need for any browser or server, but still running in the real (non-mocked) ASP.NET runtime. - You may refer to the above post to learn more about the rationales, various possibilities and technical details of this so-called MvcIntegrationTestFramework
(and to see some more examples of what you can do with it).
To integrate the framework more tightly with a Gallio/MbUnit context, I found it useful to write a base class for a custom test fixture, which basically is just a wrapper around the MvcIntegrationTestFramework
. As a result, you are able to write tests like this against your MVC app (remember: without browser or server or whatever, it just works...):
[Test]
public void SimulateRequestToRootUrl()
{
RunSession(session =>
{
RequestResult result = session.ProcessRequest("~/");
var controller = result.ActionExecutedContext.Controller as HomeController;
Assert.IsNotNull(controller);
var viewResult = (ViewResult)result.ActionExecutedContext.Result;
Assert.AreEqual("Index", viewResult.ViewName);
Assert.AreEqual("Welcome to ASP.NET MVC!", viewResult.ViewData["Message"]);
Assert.IsTrue(result.ResponseText.Contains("<!DOCTYPE html"));
});
}
...or, testing a log-in/anti-forgery mechanism, like this:
[Test]
public void TryingToAccessSecuredPageWithoutLoggingInIsBeingRedirected()
{
RunSession(session =>
{
RequestResult result = session.ProcessRequest(SecuredUrl);
Assert.IsTrue(result.Response.IsRequestBeingRedirected);
});
}
[Test]
public void CanAccessSecuredPageAfterLoggingIn()
{
RunSession(session =>
{
string loginRedirectUrl = session.ProcessRequest
(SecuredUrl).Response.RedirectLocation;
string loginPageResponseText = session.ProcessRequest
(loginRedirectUrl).ResponseText;
string antiForgeryToken = MvcUtils.ExtractAntiForgeryToken(loginPageResponseText);
var formData = new NameValueCollection
{
{ "username", "thomas" },
{ "password", "blah" },
{ "__RequestVerificationToken", antiForgeryToken }
};
session.ProcessRequest(loginRedirectUrl, HttpVerbs.Post, formData);
RequestResult result = session.ProcessRequest(SecuredUrl);
Assert.AreEqual("Hello thomas!", result.ResponseText);
});
}
The above code is self-explaining, I think. To make it work, your custom test fixture has to be derived from the MvcIntegrationFixture
base class, which only needs the web app's base directory provided in its c'tor:
public class MvcIntegrationFixture : MvcIntegrationFixtureBase
{
public MvcIntegrationFixture()
: base(Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory +
"\\..\\..\\..\\MvcIntegrationFixtureDemo"))
{
}
}
Besides wrapping the MvcIntegrationTestFramework
, the MvcIntegrationFixtureBase
class also takes care of such things like copying the required binaries to the application's bin folder, or re-catching possible assertion exceptions across app-domain boundaries. Please refer to the XML-documentation in the sample code for details.
The Sample Solution
The sample code (VS 2008 solution) is available here. It contains the (fully documented) MvcIntegrationFixtureBase
base class shown here along with some more usage examples.
<iframe marginwidth="0" marginheight="0" src=""http://ads.geekswithblogs.net/a.aspx?ZoneID=5&Task=Get&PageID=31016&SiteID=1"" frameborder="0" width="1" scrolling="no" height="1" />