Click here to Skip to main content
15,879,096 members
Articles / Web Development / ASP.NET

Integration Testing an ASP.NET MVC Application Without Web Server or Browser

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
31 Jul 2010CPOL2 min read 10.5K   4  
Integration testing an ASP.NET MVC application without Web Server or browser

While many of the single components of an ASP.NET MVC applications 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...):

C#
[Test]
public void SimulateRequestToRootUrl()
{
    RunSession(session =>
    {
        // Request the root URL
        RequestResult result = session.ProcessRequest("~/");
 
        // assertions about the controller that was created to fulfill the request...
        var controller = result.ActionExecutedContext.Controller as HomeController;
        Assert.IsNotNull(controller);
 
        // assertions about the ActionResult...
        var viewResult = (ViewResult)result.ActionExecutedContext.Result;
        Assert.AreEqual("Index", viewResult.ViewName);
        Assert.AreEqual("Welcome to ASP.NET MVC!", viewResult.ViewData["Message"]);
 
        // assertions about the rendered HTML...
        Assert.IsTrue(result.ResponseText.Contains("<!DOCTYPE html"));
    });
}

...or, testing a log-in/anti-forgery mechanism, like this:

C#
[Test]
public void TryingToAccessSecuredPageWithoutLoggingInIsBeingRedirected()
{
    RunSession(session =>
    {
        RequestResult result = session.ProcessRequest(SecuredUrl);
 
        Assert.IsTrue(result.Response.IsRequestBeingRedirected);
    });
}
 
[Test]
public void CanAccessSecuredPageAfterLoggingIn()
{
    RunSession(session =>
    {
        // First redirect to log on page and get an anti forgery token               
        string loginRedirectUrl = session.ProcessRequest(SecuredUrl).Response.RedirectLocation;
        string loginPageResponseText = session.ProcessRequest(loginRedirectUrl).ResponseText;
        string antiForgeryToken = MvcUtils.ExtractAntiForgeryToken(loginPageResponseText);
 
        // Post the login form with the user credentials and the verification token
        var formData =  new NameValueCollection
        {
            { "username", "thomas" },
            { "password", "blah" },
            { "__RequestVerificationToken", antiForgeryToken }
        };
        session.ProcessRequest(loginRedirectUrl, HttpVerbs.Post, formData);
 
        // Now, after logging in, go to the secured page again...
        RequestResult result = session.ProcessRequest(SecuredUrl);
 
        // ...this time everything should be fine.
        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:

C#
public class MvcIntegrationFixture : MvcIntegrationFixtureBase
{
    public MvcIntegrationFixture()
        : base(Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory +
                                "\\..\\..\\..\\MvcIntegrationFixtureDemo"))
    {
    }
 
    // Tests go here...

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 here shown (fully documented) MvcIntegrationFixtureBase base class along with some more usage examples.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Freelancer
Germany Germany
I'm a freelance software developer/architect located in Southern Germany with 20 years of experience in C++, VB, and C# software projects. Since about ten years I do all my coding exclusively in C# for business application development - and nowadays Python for Data Science, ML, and AI.
I am especially dedicated to Test-driven development, OO architecture, software quality assurance issues and tools.

Comments and Discussions

 
-- There are no messages in this forum --