Testing Custom Server Control Code That Depends On Its Container / Parent (the Page Object)
The issue of faking the Page object cropped up recently for me when building a server control library. I added client-side event attributes to my controls and wanted to write a test that essentially verified the expected html had been added to the HttpResponse. I hit a roadblock when I got object reference errors for the control's Page object. Note that I am not simply testing ASP.Net API - I will gladly give the ASP.Net team the credit they deserve and regard their API as capable code. I wanted to test scenarios that were intertwined with that code, however. Verifying a client-side event name in a collection was added or removed from both the Attributes collection and my custom collection, or verifying what the Attributes collection contained are examples. To explain, consider the following code, where I attempt to add an Attribute to a TextBox control, which ultimately invokes a PostBack to be handled server-side:
if (string.IsNullOrEmpty(this.Attributes[ClientSideEvent]))
{
string eventHandler = this.Page.ClientScript.GetPostBackEventReference(this, eventArgPrefix + ClientSideEvent, false);
this.Attributes.Add(ClientSideEvent, eventHandler);
}
Calling this code from NUnit will throw an error for the Page object when trying to get the PostBackEventReference string. The first and arguably easiest way to skirt this is to simply check whether this.Page is null. Temporarily shelving the KISS principle, I'll continue the discussion. Looking at the second code sample shows that I have extracted the implementation of retrieving the eventHandler string.
private string GetPostBackEventReference(string ClientSideEvent)
{
string ret_val = "";
if ((this.Page == null) ||
(this.Page.Form == null))
{
ret_val = eventArgPrefix + ClientSideEvent;
}
else
{
ret_val = this.Page.ClientScript.GetPostBackEventReference(this, eventArgPrefix + ClientSideEvent, false);
}
return ret_val;
}
This is all well and good, but what about when checking for if the current browser supports JavaScript? The ASP.Net 2.0 method for performing this check is no longer checking Browser.JavaScript. If you try to build your server control solution using Browser.JavaScript, you'll notice this property is now deprecated. Clicking to the definition of this property gets you the following:
[Obsolete("The recommended alternative is the EcmaScriptVersion property. A Major version value greater than or equal to 1 implies JavaScript support. http://go.microsoft.com/fwlink/?linkid=14202")]
public bool JavaScript { get; }
So then, now we have to do something like the following:
private Boolean BrowserSupportsJavascript
{
get
{
if (this.Context != null &&
this.Context.Request != null &&
this.Context.Request.Browser != null &&
this.Context.Request.Browser.EcmaScriptVersion != null)
{
return (this.Context.Request.Browser.EcmaScriptVersion.Major >= 1);
}
else
{
//for testing...
return true;
}
}
}
That looks like a lot of fun! This no longer seems the simplest solution, so we can throw those KISS principles out the window. Can we just assume that if the Page or Context object exists, then all of its properties and sub-properties are going to be there for us? My assumption is yes, but assuming doesn't sound like the best solution. After all, I want to write a test as the skeptical code-tester, right? I'd like to mention now that it is a shame the Page.VerifyRenderingInServerForm or comparable method does not return a Boolean. Not only that, but it would also be much appreciated if the ClientScriptManager class would implement an interface I could create a test helper from. So what are our better options? I decided that it may be best to add an isTest private flag to check when dealing with these scenarios. The GetPostBackEventReference function now looks like:
private bool isTest = false;
private string GetPostBackEventReference(string ClientSideEvent)
{
string ret_val = "";
if (this.isTest)
{
ret_val = eventArgPrefix + ClientSideEvent;
}
else
{
ret_val = this.Page.ClientScript.GetPostBackEventReference(this, eventArgPrefix + ClientSideEvent, false);
}
return ret_val;
}
If this isTest flag is private, then I'll need to set it via Reflection in my unit test, and hope that nobody using my control library messes with it. The unit test SetUp code then becomes:
private const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
private ClientSideMadnessTextBox ctrl;
[SetUp]
public void SetUp()
{
ctrl = new ClientSideMadnessTextBox();
typeof(ClientSideMadnessTextBox).GetField("isTest", bindingFlags).SetValue(ctrl, true);
...
}
You could take this a step further if you liked and have a utility class which contains all the methods you need to fake (such as GetPostBackEventReference or GetWebResourceUrl), pass a type to this utility class, and then use reflection to call the method on the type specified (be it MyClientScriptManager or the real ClientScriptManager) but you would still be dependent upon the isTest private member, so this seems overly complex. Just a thought, though...
Has anyone else come across this? This looks like a Dependency Injection scenario to me, and I'm wondering what others have done. Surely the ASP.Net team tests their code, so I'm curious how they did this. Any advice is appreciated.
Additional Resources