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 Request
property:
I use a similar trick with Response
and 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:
Install-Package WebFormsTest
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.