rickardnilsson.net is a weblog and the online home of web developer and father of three, Rickard Nilsson... More
Rickard blogs about creating software solutions using ASP.NET and agile practices.
Much of the legacy ASP.NET code I’ve seen is littered with calls to methods on the HttpServerUtility class,
Server.MapPath(…)
Why, if your suite has thousands of tests and many calls IO or datebases, the tests will run slowly, and the developers on the team won’t run them as often. Ultimately, you may loose your investment in automated testing because it isn’t providing the promised feedback.
If the code behind code calls Server.MapPath() it is actually calling the Server property on the Page base class which returns HttpContext.Current.Server. This is an instance of the HttpServerUtility class, which is sealed and thus pretty impossible to fake out*.
In the namespace System.Web.Abstractions, which is part of ASP.NET 3.5, lives an abstraction of the HttpServerUtility, called HttpServerUtilityBase. It has a concrete implementation named HttpServerUtilityWrapper that takes an HttpServerUtility instance as a constructor parameter, as follows:
public sealed class HttpServerUtility { // ... }
public abstract class HttpServerUtilityBase { // ... }
public class HttpServerUtilityWrapper : System.Web.HttpServerUtilityBase { public HttpServerUtilityWrapper(HttpServerUtility httpServerUtility) {} // ... }
By leveraging a simple form of dependency injection we can preserve the old code as a first step of refactoring, and using an overloaded constructor to inject the fake object in our unit test.
public class Presenter { private HttpServerUtilityBase Server; public Presenter(HttpServerUtilityBase httpServerUtility) { Server = httpServerUtility; } public Presenter() { Server = new HttpServerUtilityWrapper(HttpContext.Current.Server); } public void PageLoad() { var path = Server.MapPath(…) } }
Now, in a unit test for the Presenter class we can inject a fake server utility, which won’t call any IO.
[Test] public void PageLoad_WhenCalled_ExpectedBehavior() { var fakeServerUtility = new HttpServerUtilityFake(); // implemented in the test suite var presenter = new Presenter(fakeServerUtility); presenter.PageLoad(); // Assert expected behavior }
Instead of implementing your own fake you can easily use your preferred isolation (mocking) framework of choice.
The goal is to isolate the class under test from all of its dependencies, weather they call IO, a database, a third party component, or even statics or touch static state. The point is that we want to assert that the class under test behaves as expected, not how the underlying framework behaves.
By leveraging the System.Web.Abstractions namespace we can preserve much of the existing ASP.NET code while covering it with tests.
_________ * Unless using TypeMock Isolator