Using WebServer.WebDev For Unit Tests

A Scanning Test - From http://www.sxc.hu/photo/517386

Last night a unit test saved my life (with apologies). Ok, maybe not my life, but the act of writing some unit tests did save me the embarrasment of an obscure bug which was sure to hit when I least expected it.  It is cases like this that made me into such a big fan of writing automated unit tests.

Not too long ago I wrote a C# Akismet API for Subtext. In writing the code, I followed design principles focused on making the API as testable as possible. For example, I applied Inversion of Control (IOC) by having the AkismetClient constructor take in an HttpClient instance as an argument. The HttpClient instance is responsible for making the actual HTTP request.

This allowed me to use Rhino Mocks to replace the HttpClient with a mock enabling me to build unit tests that ensured that the Akismet API was doing the right thing without having to make any actual web requests.

Of course this approach only delays the inevitable. I still want to have an automated test for the HttpClient class.

So last night, I took a step back and revisited this excellent post by Scott Hanselman in which he shows how to use Cassini in your unit tests. I decided to update his pioneering approach to use the latest incarnation of Cassini, WebServer.WebDev. I also decided to refactor what he did into a reusable TestWebServer class in order to make the barrier to entry in using it as low as possible.

Setup

WebServer.WebDev is the built in Web Server (formerly known as Cassini) used by Visual Studio.NET 2005. The main functionality of the server is located in WebDev.WebHost.dll. You can find this assembly in the GAC. On my machine, it is located in the following directory:

c:\WINDOWS\assembly\GAC_32\WebDev.WebHost\8.0.0.0__b03f5f7f11d50a3a\

Note that the .NET framework installs an explorer extension for the GAC so you won’t see this directory using Windows Explorer. I navigated to the directory using the command prompt.

Setting up the test web server is a two step process once you’ve located the WebDev.WebHost assembly.

  1. Copy WebDev.WebHost into your unit test project and add a reference to it.
  2. Add the TestWebServer.cs file into your unit test project.

Note: To make this really reusable, you could drop this class into a separate Unit Test Helper assembly that you reference in your unit test projects. If you do go that route, be sure to heed the “//NOTE” I left for you in the ExtractResources method.

TestWebServer Usage

The following shows a couple of ways you can use this test web server in your own unit tests. If you have a single test in a fixture that needs to use the server, you can do something like this:

using (TestWebServer webServer = new TestWebServer())
{
    webServer.Start();
    webServer.ExtractResource("ResourcePath.SomePage.aspx"
      , "SomePage.aspx");
    string response = webServer.RequestPage("SomePage.aspx");
    Assert.AreEqual("Done", response);
}

If you have a set of tests that need to use the web server, I suggest using the [TestFixtureSetUp] and [TestFixtureTearDown] attributes to start the web server just once for all the tests.

private TestWebServer webServer = new TestWebServer();
private Uri webServerUrl;

[TestFixtureSetUp]
public void TestFixtureSetUp()
{
    this.webServerUrl = this.webServer.Start();
}

[TestFixtureTearDown]
public void TestFixtureTearDown()
{
    if(this.webServer != null)
        this.webServer.Stop();
}

I added several helper methods to the TestWebServer class based on what Scott did.

ExtractResource takes in the full path to an embedded resource and extracts it into the test webroot. In the first code example above, I extracted an embedded resource into a file named SomePage.aspx. Be sure to call this method after the webserver is started.

RequestPage has two overloads. One which makes a simple GET request to the test web server, and the other which makes a POST request.

Discussion

In the past, I have gone to great lengths to not using a web server to unit test my code, as that takes us more into the realm of Integration testing. A while ago I wrote a post on how to simulate the HttpContext for unit tests without using a web server. This approach has been improved upon in the Subtext unit test codebase and has served me well.

But even that approach can only go so far. As I pointed out in my post on a Testing Mail Server, it’s a good thing to abstract out these extensibility points using an interface or a provider. But at some point, you have to test the concrete implementation. I can’t keep delegating functionality endlessly to another abstraction. Somebody has to make a real HTTP request.

So consider this approach the method of last resort.

Download

The test webserver class can be downloaded here from my companys tools site.

What others have said

Requesting Gravatar... DotNetKicks.com Dec 12, 2006 4:28 PM
# Using WebServer.WebDev For Unit Tests
You've been kicked (a good thing) - Trackback from DotNetKicks.com
Requesting Gravatar... Simone Busoli Dec 12, 2006 5:14 PM
# re: Using WebServer.WebDev For Unit Tests
Nice post Phil. In case you don't know about the Selenium project, in particular Selenium Remote Control, it's a very useful framework for interctively testing web applications.

http://www.openqa.org/selenium/
Requesting Gravatar... Jason Haley Dec 12, 2006 8:25 PM
# Interesting Finds: December 12, 2006
Requesting Gravatar... Craig Dec 13, 2006 8:50 AM
# re: Using WebServer.WebDev For Unit Tests
One thing I would watch out for is the fact that WebDev.WebServer behaves significantly differently from ASP.NET running under IIS. I've hit all sorts of differences, among which are the fact that the managed pipeline handles *all* requests, not just the ones mapped to ASP.NET. I forget many of the others, but I do know that I don't trust webdev.webserver much.

Which isn't to say what you're using it for isn't useful.
Requesting Gravatar... Haacked Dec 13, 2006 10:45 AM
# re: Using WebServer.WebDev For Unit Tests
Good point. In fact, I've tried to make our web.config handle the differences. For example, we map static file extensions to the StaticFileHandler.

I trust it enough to use it for day to day development. I then use IIS for testing before releasing.
Requesting Gravatar... Community Blogs Dec 14, 2006 12:32 PM
# Indulging My OCD Using TestDriven.NET With NCoverExplorer
I don’t suffer from classic OCD (Obsessive Compulsive Disorder), but I do sometimes have OCD tendencies.
# WebDev.WebServer for easy website hosting
Visual Studio 2005 Websites and Web Application projects use the ASP.NET Development Server as a light-weight
Requesting Gravatar... Laboremus Dec 16, 2006 9:13 AM
# re: Using WebServer.WebDev For Unit Tests
Can't download the TestWebServer.cs file. I seems to have all criterias met - JS enabled and no visible script blockers, but still doesn't work :(
Requesting Gravatar... Sontek (John M. Anderson) Jan 02, 2007 2:42 PM
# WebDev.WebServer Tricks
WebDev.WebServer Tricks
Requesting Gravatar... Martin Bayly Jun 01, 2007 11:23 AM
# re: Using WebServer.WebDev For Unit Tests
Seems to work great apart from when running multiple tests via the nunit-console.

I was starting/stopping the web server in the [TestFixtureSetUp] methods of each test case.


For some reason I get the following exception after the test are run if I have multiple test classes using this approach:

[exec] Tests run: 14, Failures: 0, Not run: 0, Time: 8.078 seconds
[exec] Unhandled exceptions:
[exec] 1) : System.AppDomainUnloadedException: Attempted to access an unloaded AppDomain.

The weird thing is it doesn't seem to stop the tests - but nunit returns an error so it breaks my build.

Initially I was able to work around this by using an NUnit [SetUpFixture] to start the webserver once for all tests in the assembly.

However, I then added another test assembly using the same technique and the AppDomainUnloadedException came back again.
Don't understand enough about AppDomains to understand what could be causing it. In the end I had to work around it by starting independent instances of the nunit console to run each test assembly.
Requesting Gravatar... Joe Haynes Jul 08, 2007 4:26 AM
# H._Donald_Wilson
Lycophotia porphyrea Silver Slipper Casino Manson structure Captain Moroni WUPW-TV Robertson Howard Rene Guyot Abstraction model checking Bloomfield Township (CDP), MI Joe Haynes
Requesting Gravatar... Graham Jan 25, 2008 3:01 AM
# re: Using WebServer.WebDev For Unit Tests
Does this approach lend itself to the use of mocks or will cross AppDomain issues cause a problem?

What do you have to say?

(will show your gravatar)
Please add 7 and 4 and type the answer here: