Tuesday, May 19, 2015

Unit testing ASP.Net Web Forms

Scenario

We have a simple legacy web application created using ASP.Net Web Forms. It works well and doing its duties without fail. Mainly it deals with simple pages not many transactions involving many pages. But rarely there are changes coming in. Even if the changes come frequently, the changes are small changes which are not affecting the core functionality of web site. Being a passionate software engineer, we wanted to bring innovation into the project such as refactoring to patterns and more object orientation.

Problem

We are confident on the refactoring we are doing in the site. But still there is a factor of fear that whether those changes will break the other pages in the site or not? Basically need to make sure all the links are working. Normally after a change we manually click on every link and make sure they are all loading.

That is a repetitive work. No software engineer should be doing repetitive work. We need to solve this in less cost. As usual since its legacy application, there is no budget to buy tools or automated testing service.

Approaches

1.Write a tool to validate links

We can write a tool which will parse the html of default.aspx and make sure all the links are working properly ie returning valid html contents. Can use XMLHttpRequest or WebClient class to accomplish it. Can configure the tool to run as post build action.

Challenges are in validation because there is no guarantee that HTML pages can be easily parsed with XML classes. There is a nuget called HTML Agility pack which we can try here.

2.Write unit tests on existing Web Forms app

Write unit tests for all the pages and at least make sure all the are passing.

After evaluating, I feel the unit tests are the way to do developer testing. But is my legacy application written as testable. Absolutely no. Now what we can do? One way is to rewrite as ASP.Net or use the Url based unit test. Refactoring the business logic and testing those is not applicable as the site doesn't have complex logic and more than that we need to make sure the pages are all working after our change.

3.Convert into ASP.Net MVC and unit test

Though this is the perfect solution most of the time this will not work out because.
  1. There may not be enough budget for maintaining legacy app or convert into ASP.Net MVC because from the business perspective, the site is doing its duty properly
  2. There might be links already bookmarked by users. Those will be invalid unless there is a rerouting mechanism in new MVC application.

Solution - Unit test ASP.Net Web Forms

The concept of unit test is applied to one level above the normal unit testing of classes and methods. Here we test the ASP.Net Pages itself. We can check particular properties of Page is set when rendered for a particular request url. Below are the steps to create unit tests for ASP.Net Web pages

1.Create UnitTest Project

Create normal unit test project from VisualStudio->New->Project dialog which we used to create for other scenarios such as class libraries or MVC.

2.Refer System.Web

Reference to Syste.Web.dll in UnitTest project is required if we want to hold the Page variable for assert.

3.Write test method with special attributes

Now comes the testmethod same as in normal test project. Here additions are 2 attributes HostType and UrlToTest. They specifies where the web page is hosted and what page corresponding test method is going to test. Below is one scenario where test method is testing a page hosted in Local IIS server.

string url = "http://localhost/joymononline/"
[TestMethod]
[HostType("ASP.NET")]
[UrlToTest(baseUrl+"links.aspx")]
public void WhenLinksPageIsRequested_ShouldReturn200AndContent()
{
    Page page = TestContext.RequestedPage;
    string url = Path.Combine(baseUrl, "links.aspx");
    //Assert.IsTrue(UrlIsValid(url));
    Assert.IsTrue(page.Title.Equals("Joymon Online | Links"));
}

If you are looking for how to test a web application running from development server, please have a look at the below links.


I strongly recommend developing by pointing to local IIS server, because IIS is the way your application is going to be served to end users. Otherwise there will be scenarios of  "works on my machine" which is really tough to deal with.

Debugging IIS hosted Web Forms Unit test

By default if we put a break point in ASP.Net Web Forms unit test where the site is hosted in IIS, it wont get hit. We need to attach to IIS process w3wp.exe.

So put a Debugger.Break or Debugger.Launch. This will open up an error dialog and from there we can attach to W3WP.exe.1

Also need to make sure that the dlls are compiled in debug mode.

Issues

Sometime there may be issues when running the unit tests. One error what I faced was below.

The URL specified ('http://localhost/joymononline') does not correspond to a valid directory. Tests configured to run in ASP.NET in IIS require a valid directory to exist for the URL. The URL may be invalid or may not point to a valid Web application

There are multiple reasons for this.

First we need to run Visual Studio in admin privilege. 

Second we need check whether the below entry is logged into event log viewer -> application  node

(QTAgent32_40.exe, PID 12920, Thread 14) WebSites.GetWebServer: failed to create AspNetHelper: Microsoft.VisualStudio.Enterprise.Common.AspNetHelperException: The website metabase contains unexpected information or you do not have permission to access the metabase.  You must be a member of the Administrators group on the local computer to access the IIS metabase. Therefore, you cannot create or open a local IIS Web site.  If you have Read, Write, and Modify Permissions for the folder where the files are located, you can create a file system web site that points to the folder in order to proceed. ---> System.Runtime.InteropServices.COMException: Unknown error (0x80005000)
   at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
   at System.DirectoryServices.DirectoryEntry.Bind()
   at System.DirectoryServices.DirectoryEntry.get_IsContainer()
   at System.DirectoryServices.DirectoryEntries.ChildEnumerator..ctor(DirectoryEntry container)
   at System.DirectoryServices.DirectoryEntries.GetEnumerator()
   at Microsoft.VisualStudio.Enterprise.Common.IISHelper.GetWebServerOrdinal(Uri site)
   --- End of inner exception stack trace ---
   at Microsoft.VisualStudio.Enterprise.Common.IISHelper.GetWebServerOrdinal(Uri site)
   at Microsoft.VisualStudio.Enterprise.Common.IISHelper.get_WebServerOrdinal()
   at Microsoft.VisualStudio.Enterprise.Common.IISHelper.get_RootPath()
   at Microsoft.VisualStudio.Enterprise.Common.IISHelper.get_PhysicalPath()
   at Microsoft.VisualStudio.Enterprise.Common.AspNetHelperMan..ctor(Uri uri, BasicAuthCredential credential, Int32 frameworkMajorVersion)
   at Microsoft.VisualStudio.Enterprise.Common.AspNetHelperMan..ctor(Uri uri, BasicAuthCredential credential)
   at Microsoft.VisualStudio.Enterprise.Common.AspNetHelper.Create(Uri uri, BasicAuthCredential credential)
   at Microsoft.VisualStudio.TestTools.HostAdapters.WebSites.GetWebServer(String webServerName, WebServerType webServerType, String urlToTest, String pathToWeb, String webAppRoot, BasicAuthCredential credential, Context context, WebSiteConfigurationType webSiteConfigType, Origin origin)

If we can see the above, we can try enabling IIS 6 compatibility. This is applicable if we are running on Windows 7 with IIS 7 or above.

Go to Control Panel->Programs and Features->Turn Windows Featuers On or Off
Then
Internet Information Services->Web Management Tools->IIS 6 Management Compatibility Tick the "IIS Metabase and IIS 6 configuration compatibility" option and install it.

Some users says that we need to enable everything in IIS 6 Management Compatibility.2

References

1http://forums.asp.net/t/1246417.aspx?How+to+Debug+unit+tests+for+ASP+NET+web+application
2http://stackoverflow.com/questions/9065937/asp-net-unit-testing-windows7-iis7

No comments: