ASP.NET applications are still difficult to write automated tests against. Other languages (notably Ruby on Rails) have powerful mechanisms to perform not only unit-level testing, but also integration testing: fetching a set of webpages, performing postbacks, and verifying the HTML from the responses along the way.
MVC3 is partially testable: you can create controllers and request views, even pull items out of the ViewBag. That’s great, but for many applications, it’s not enough; you want to be able to actually read the underlying HTML and check for the presence or absense of certain HTML content.
Thankfully, there is an answer. It’s not simple, but it is relatively straight-forward: deploy your debug directory of your web application onto IIS, and use a .NET conversion of HtmlUnit (a GUI-less browser written in Java) to run on IKVM, a .NET implementation of the JRE.
Credit for this amazing solution goes to Steve Sanderson, who documented the process (and built the binaries) on his blog. Essentially, you need to:
- Deploy your debug environment into IIS (giving you the benefit of seeing real IIS errors that don’t appear in Casini)
- Download the HtmlUnit and IKVM binaries
- Add all the appropriate references to your test project
- Compile, execute, and rejoice
Steve’s page does a great job of xplaining all the steps; just note that he has a link to the IKVM and HtmlUnit binaries at the end of his post (so you don’t need to build them yourself).
Once you have your app deployed, testing can be as simple as:
[TestMethod]
public void RequestingSecureUrlRedirectsToLogOn() {
WebClient webClient = new WebClient();
HtmlPage page = (HtmlPage)webClient.getPage("http://localhost/some/secure/action");
WebResponse response = page.getWebResponse();
Assert.IsTrue(response.getRequestUrl().toString().EndsWith("/Account/LogOn"));
}
Run your tests (Visual Studio test framework in my case), and, tada! More complicated tests are possible (filling out forms, clicking buttons, checking response HTML), all though the HtmlPage API. For example, we could have extended that test to include:
((HtmlInput)page.getElementById("UserName")).setValueAttribute("bob");
((HtmlInput)page.getElementById("Password")).setValueAttribute("invalid");
HtmlPage resultsPage = (HtmlPage)loginPage.getElementById("submit").click();
string content = resultPage.asXml();
Assert.IsTrue(content.Contains("Login was unsuccessful."));
In this case, we filled in the UserName and Password fields, clicked Submit, and checked the HTML of the resulting page — that it contained an error message.
Pretty slick stuff. I highly recommend it if unit testing is something you prioritize. The potential for integration testing is pretty awesome.
Related posts: