Saturday, 29 December 2007

With Visual Studio 2008 it is now possible to unit test you ASP.Net applications. This is achieved using three new method attributes HostType, UrlToTest and AspNetDevelopmentServerHost, along with the TestContext  and PrivateObject classes. Using the combination of these attributes and classes it is possible to run tests for an ASP.Net page in the development server included with Visual Studio or IIS.

Example 1:
This is a very simple web page, it consists of a button that when clicked will display some text in a label.

1.      Create a new Web application project in Visual Studio 2008.

2.      Add a test project to the solution with a test class called DefaultUnitTests.cs.  Your solution should now contain the following.

3.      We’ll start by defining the test. So open DefaultTests.cs. The first thing we need to do is add a using directive for Microsoft.VisualStudio.TestTools.UnitTesting.Web this namespace contains all the attributes relating to testing web pages. You will also need to add using directives for System.Web.UI and System.Web.UI.WebControls you need these as you will be using web controls in the test method.

4.      We are now able to add the test method. As this test will be executed against a web page there are a number of attributes that must be added to the method so that the tests are executed in the correct environment. The first of these is the HostType attribute which allows you to specify the type of host that the test will run in. As we want to run our tests in the ASP.Net engine we will specify the string ASP.Net.

5.      The next attribute that needs to be specified is UrlToTest. When using the ASP.Net development server the port that the ASP.Net development server runs on is randomly assigned, therefore when testing with the ASP.Net development server the URL will be fixed up to use the correct port number. However if you run your tests in IIS then no fix up is needed.

6.      The final attribute that needs to be applied is AspNetDevelopmentServerHost. This is used to tell the ASP.Net development server where the web site is located on disc, if you plan to test only with IIS then you can omit this attribute.

7.     Now that all the attributes have been applied to the method we can define the method body. This is where we will use the TestContext and PrivateObject classes to get a handle to the page and invoke methods.

8.      The TestContext contains the RequestedPage property that allows access to the Page object that was created to server the URL requested by the test. Once you have a handle on the Page object you can use the pages FindControl method to access the various controls on the page.

9.      Next create an Instance of a PrivateObject passing the page as a parameter to the PrivateObjects constructor. Once the PrivateObject has been created we can use the Invoke method to call any method of the page object. In this case we will be calling the event handler of the buttons click event, thus simulating the process of the user clicking the button. For more information on PrivateObjects see my earlier post. The code snippet below shows the full test method.

[AspNetDevelopmentServerHost(@"C:\code\AspNetTDD1\AspNetTDD1", "/AspNetTDD1")]
public void TestMethod1()
Page page = _testContextInstance.RequestedPage;

Button button = page.FindControl("Button1") as Button;
Label label = page.FindControl("Label1") as Label;

PrivateObject po = new PrivateObject(page);

po.Invoke("Button1_Click", button, EventArgs.Empty);

Assert.AreEqual("Did you hear that? They shut down the main reactor.
We'll be destroyed for sure", label.Text);

10.  Running the tests at this stage will cause the tests to fail with a null pointer exceptions as the controls and event handler have yet to be defined.

11.  We will now add the controls to the page and define the event handler. Your aspx page should contain a definition for a label and button control similar to this.

   <asp:Label ID="Label1" runat="server" />
<br />
<asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Button" />

12.  Double click on the button to add an on click event handler.

13.   If you run the tests run (keyboard shortcut: Ctrl + r + a) or click Tests -> Run -> All tests in solution.

14. The test will fail as the label contains an empty string. Therefore we need to add the following to the button click event handler

protected void Button1_Click(object sender, EventArgs e)
     // can use guess the film?
     Label1.Text = "Did you hear that? They shut down the main reactor.
                   We'll be destroyed for sure";

15. Build and run the test again and it should now pass for you.

This is a very simplistic example of unit testing an Asp.Net page, but this has been done on purpose so that you can see the nuts and bolts of the technology, this should give you an idea of the attributes needing applied to a method that tests ASP.Net pages, you should also now understand how to obtain a handle to the Page object and access the controls and event handlers contained within the page.

Example 2:
In this example I will use the same principles as described in example 1 but I  will now use the UrlToTest attribute to execute a different URL for the two test methods. The page will look for a parameter from the query string and display this in label. 

As the process of creating the solution is identical to that of example 1 I will outline only the additions to this solution. You can download complete solution file for both example 1 and 2 at the end of this post.

  •  We now have two test methods one of which will pass and the other will fail, this is to illustrate that it is possible to invoke each test against a different URL in this case one with the parameter included in the URL and the other without the parameter. The code snippet below shows one of the test methods with the UrlToTest attribute applied.

[AspNetDevelopmentServerHost(@"C:\code\AspNetTDD2\AspNetTDD2", "/AspNetTDD2")]
public void TestMethod1()
     Page page = _testContextInstance.RequestedPage;

     // need to manually invoke the Page_Load method.
     PrivateObject po = new PrivateObject(page);
     po.Invoke("Page_Load", page, EventArgs.Empty);

     Label label = page.FindControl("Label1") as Label;

     Assert.AreEqual("Hello", label.Text);

  • I have also had to use a PrivateObject to invoke the Page_Load method. When unit testing a page the methods normally called as part of the pages life cycle are not invoked therefore you must use a PrivateObject to manually invoke the methods if their execution is required.

Example 3:
In this example I will show a slightly more realistic implementation of an ASP.Net page. The page uses a data access component to retrieve a string and display the value in a label on the page. In this example I will again touch on the use of a PrivareObject and mock objects. You can download the complete solution for this example at the end of this post.

Again as the process of creating the solution is identical to that of Examples 1 and 2 I will therefore skip the step by step explanation and explain only the more interesting parts of the code, you can download the complete solution at the bottom of this post.

  • The basic class diagram below shows the basic structure of the storage components used in this example.
  •  Implementing the RealDataAccess component is unnecessary for this example therefore I will not provide an implementation for this. In a real application the RealDataAccess class could involve connecting to a database which would obviously add an extra layer of complexion that would  be a distraction from the purpose of this example.
  • As always the first thing we’ll define is the test method. The code snippet below shows this test. As this method will be testing and ASP.Net web page I need to add the attributes TestMethod (mark this as a test method), HostType (indicate that this test should run in the ASP.NET), UrlToTest and AspNetDevelopmentServerHost (needed as I’m running my tests in the ASP.Net development server, if you want to use IIS you can omit this attribute).

[AspNetDevelopmentServerHost(@"C:\code\AspNetTDD3\AspNetTDD3", "/AspNetTDD3")]
public void TestMethod1()
Page page = _testContextInstance.RequestedPage;

Button button = page.FindControl("Button1") as Button;
Label label = page.FindControl("Label1") as Label;

PrivateObject po = new PrivateObject(page,
new PrivateType(page.GetType().BaseType));

po.SetField("_dataAccess", new MockDataAccess());
po.Invoke("Button1_Click", button, EventArgs.Empty);
       Assert.AreEqual("Welcome young Skywalker," +
"the force is with you but you are not a Jedi."

  •  The actual test method should be straight forward as it reuses techniques from previous examples. First the TestContext is used to get a handle on the page object, from this a handle can be gotten to the label and button controls that have been placed on the page.
  • We then make use of a PrivateObject to set the page to use the MockDataAccess object. The code snippet below shows how the PrivateObject is created for the page. A PrivateType has to be passed to the constructor of the PrivateObject. Here I pass page.GetType().BaseType to the PrivateType constructor. This is necessary as the type of the page is generated dynamically and in this scenario I want to manipulate a private field declared in the code behind of the page therefore I need to ensure the PrivateObject is constructed with the type of the code behind otherwise I would not be able to manipulate the private field.

PrivateObject po =
PrivateObject(page, new PrivateType(page.GetType().BaseType));

  • At this point everything has been setup and configured to run in a controlled environment, as the page uses the mock data access component we know exactly what to expect.
  • To carryout the actual test we need to click the button, to do this in a unit testing scenario we don’t want to have to manual click a button each time as one advantage of unit tests is they can run unattended as part of a night build process. In order to simulate a user clicking the button we can simply use a PrivateObject to invoke the click handler of the button.
  • Once the button has been clicked it is simply a matter to checking that the label’s text has been updated with the correct text.

Hopefully from these simple examples you will have gained an appreciation of the features now available with Visual Studio 2008 that allow you to conduct unit testing on your ASP.Net applications.

Example Solutions (39.09 KB) (32.71 KB) (64.01 KB)