I’m a huge fan of unit testing… its my safety net, allowing me to make changes to an application without fear that I’ve broken core functionality. With my favorite web development framework, ASP.NET, its been very difficult to build unit tests for the server-side code that you write for the Web Forms UI framework.
I decided to do some research and start doing something about that.
This is not a project I was assigned by my employer, nor is it sponsored by my employer. I was writing some ASP.NET Web Forms code and decided to dig further into being able to unit-test my code. The only Microsoft resource used to write this code was a browse through the ASP.NET Web Forms source code, which is freely available to the public at referencesource.microsoft.com
The biggest things that I want to be able to unit-test in my application are the interactions with the browser: is the Page processing the postback data properly? Is the Page interacting with Querystring data correctly? These are interactions with the HttpContext object which is NOT mockable and very hard to substitute in a Page.
Some folks have approached this problem by accepting a
HttpContextBase object as an optional parameter to a new constructor to a new Page subclass. After tinkering with this approach, I decided that it didn’t entirely work for me to add this constructor.. it feels unnatural to a web forms developer. Instead, I started a new
TestablePage class that inherited from
System.Web.UI.Page and would provide a new
HttpContext to the
Context property. My testable page initially looks like this:
Not bad.. I can take my application and configure those Page items that I want to be able to test and inherit from
TestablePage instead of
Page and then it becomes easy to mock items from the Context with a test structure like this:
That’s a start… we can compose our mock
HttpContext of whatever content is needed and jam it into the
Context property. What about those other properties that route through the context like Session, Response, and Request? I’ve overridden those properties so that they use my derived property. Check out how I simplified the
I use a similar trick with
Session properties on the
TestablePage class. Now we have a reliable base-class that can be substituted for the Page object in a WebForms application and mock out these simple objects.
Not so fast… we have Page Events!
Of course the heart and soul of writing code-behind code for a WebForms application is the ability to handle and inject business logic in the four basic Page events: Init, Load, PreRender, Unload. If we’re really going to start testing a Web Forms application, we need to be able to emulate these page events and trigger them when we need them.
To make this possible, I added a method to the
TestablePage class called
FireEvent that would allow you to trigger one of these events with your own collection of event arguments:
Simple right? Now you can verify that when the
Load event is triggered that an appropriate action is taken.
Just the beginning
I know that I’m missing a lot of capabilities to be able to emulate: AutoEventWireup of events, IsPostback property and complete creation of the control hierarchy to name a few. I think this is something that can prove to be very useful for teams that are refactoring their applications so that they have reusable components that are more easily migrated to ASP.NET MVC, WebAPI, or even to ASP.NET 5.
The source code for this Web Forms Test Harness is available on my GitHub repository at: http://github.com/csharpfritz/WebFormsTest
… and the harness can be added to your unit test project (supporting .NET 4+) from NuGet with the following command:
I have marked the package and source code with the Apache license so that you can use it without limitation in your unit test projects. If you would like to contribute, please send along a pull request or an issue report. I have some more advanced features planned and I want to spend some time with my quality assurance friends to see what features can be added to this harness that may help make their lives easier too.