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 page’s 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 PrivateObject’s 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 button’s 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. [TestMethod] [HostType("ASP.NET")] [UrlToTest("http://localhost/AspNetTDD1/Default.aspx")] [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); }
[TestMethod] [HostType("ASP.NET")] [UrlToTest("http://localhost/AspNetTDD1/Default.aspx")] [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" />
<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.
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.
[TestMethod][HostType("ASP.NET")][UrlToTest("http://localhost/AspNetTDD2/Default.aspx?demoParam=Hello")][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);}
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.
[TestMethod] [HostType("ASP.NET")] [UrlToTest("http://localhost/AspNetTDD3/Default.aspx")] [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.", label.Text); }
[TestMethod] [HostType("ASP.NET")] [UrlToTest("http://localhost/AspNetTDD3/Default.aspx")] [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.", label.Text); }
PrivateObject po = new PrivateObject(page, new PrivateType(page.GetType().BaseType));
ConclusionHopefully 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.
.Net (4) ASP.Net (6) ASP.Net MVC (3) Best Practice (2) Book (4) Burn CD/DVD (1) C# (5) Career (2) Class Library (1) Community event (14) Design (1) Design Patterns (1) Ethernet (1) Home Network (1) i18n (1) Internet Explorer (1) iPod (1) JavaScript (2) jQuery (1) Library (1) Microsoft Translator (1) Ms Office (1) MVC Unit test (1) NWMTUG (6) Open Source (2) PowerShell (13) Python (1) SharePoint (1) SQL Server (1) Team System (1) Test Driven Development (7) Utility (14) Visual C++ (1) Visual Studio 2008 (11) Windows 64 bit (1) Windows 7 (10) Windows XP (3)