ASP.NET unit tests let you test classes and methods defined in an ASP.NET web site. In a post last year, I explained how to test a site's page object, which is not available for test generation like classes in App_Code are. Today I'm going to show a way to test classes defined in a page's code file (e.g. Default.aspx.cs) that are not the page class. This serves as a followup to the previous post, so you should make sure you're familiar with it first.

As decribed before, the page class is defined in the .aspx and .aspx.cs (or .aspx.vb) files for the page, and you can't generate unit tests for these files due to differences in how they are handled by Visual Studio. In addition the page class, it's possible for the .aspx.cs file to contain other classes, which will not be available for test generation either. For example, consider that you have a Default.aspx with Default.aspx.cs for its CodeFile. Default.aspx.cs contains the following classes:

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }
}

public class OtherClass
{
    public int Add(int val1, int val2)
    {
        return val1 + val2;
    }
}

Testing _Default can be accomplished as described in the earlier post, using the TestContext.RequestedPage property. Testing OtherClass, however, requires an extra step. To do this, I came up with the following test method:
[TestMethod]
[HostType("ASP.NET")]
[UrlToTest("http://localhost/TestSite")]
public void TestMethod2()
{
    string codeBehindAssembly =
        TestContext.RequestedPage.GetType().Assembly.ToString();            //1

    PrivateObject po = new PrivateObject(codeBehindAssembly, "OtherClass"); //2

    int result = (int)po.Invoke("Add", 2, 3);
    Assert.AreEqual(5, result);
}
  1. To access OtherClass, you need to know what assembly it's in. However, you can't generally know this in advance, since the assembly is created dynamically by ASP.NET. But because OtherClass is in the same assembly as the page, you can get the name at runtime with this call.

  2. Next you can use the assembly and class name to create a new PrivateObject for OtherClass. While you can't directly reference OtherClass, PrivateObject lets you access any of its members through reflection, similar to how you can use PrivateObject to access normally unavailable members of a Page object.

    Note that if OtherClass's constructor takes arguments, you can add those after the assembly and class name in the PrivateObject constructor. For example, calling new PrivateObject(codeBehindAssembly, "OtherClass", 1, "foo") has the effect of calling new OtherClass(1, "foo").

Along with previously described techniques, this enables you to test code in a variety of places in an ASP.NET site, even though the behavior of ASP.NET prevents you from referencing classes in a site directly. To summarize:
  • To test classes in the App_Code directory, use unit test generation. This creates private accessors that let you access their members.
  • To test the page class, use TestContext.RequestedPage and PrivateObject as described in the previous post.
  • To test other classes defined in the page's code file, get the assembly name via TestContext.RequestedPage, then instantiate the desired type using PrivateObject.
Kevin Cogger
SDET, Visual Studio Team System