Web Testing with MbUnit and WatiN Part 1: Keeping Your Tests Legible

We’re all quite comfortable writing unit tests to verify that a piece of code does what we think it should do, and there are many test frameworks to enable this activity. One solid solution for going one step further and writing powerful integration tests for .NET web applications is to combine WatiN, an informal .NET port of the WatiR open-source (BSD) family of Ruby libraries for automating web browsers, with the Gallio automation framework and MbUnit test library.

Gallio \ MbUnit in particular is a good choice for writing tests with WatiN as it comes with a number of utility classes that allow us to write tests against a web page that far cleaner and concise than without them. Version 2 of WatiN also provides several classes for the same purpose. To demonstrate, in this article you will

  • Create a simple web test project that uses MbUnit and WatiN.
  • Identify the code smells present in a simple web test.
  • Use the WatiN Page class to refactor the implementation details of the page being tested away from the tests.
  • Use the MbUnit sample AbstractBrowserTestFixture<TPage> class to refactor the control of the test browser’s lifecycle away from the tests.

To follow along with this tutorial, you will need the following installed

  • Visual Studio 2010 or later and .NET 4.0 or higher. You can install the free Express edition of Visual Studio 2010 here.
  • Gallio \ MbUnit. The latest release is v3.3.442 and can be downloaded here as either MSI installer or zipped binary files. The Gallio wiki covers the installation process for both.
  • WatiN. The latest release is v2.1.0.1196 and can be downloaded here.

It is also assumed that you know how to run tests with Gallio. If you don’t, this short tutorial in the Gallio wiki is a good place to start before carrying on.

Creating A Basic Web Test Project

To start then, we’ll create a simple web test project that uses MbUnit and WatiN. It will contain a single test that targets the search page at google.com.

  • Open Visual Studio and create a new C# Class Library project called WebTestDemo.
  • Add a reference to the Gallio and MbUnit assemblies to your project. If you installed Gallio with the MSI installer, you’ll find them in the GAC. If you used the zip file or don’t want to reference GAC-based assemblies, you’ll find them in %ProgramFiles%\Gallio\bin.
  • Add a reference to the WatiN.Core and Interop.SHDocVw assemblies.
    • If you downloaded the WatiN files, you’ll find them in the bin\NetXX directory where you previously unzipped the WatiN package. The assemblies are provided for .NET 2.0, 3.5 and 4.0. Be sure to select the right assemblies.
    • If you have Visual Studio 2010 with Nuget installed, you can also get Visual Studio to download the latest WatiN files and add those references by running Install-Package WatiN at the Package Manager Console prompt.

Figure 1 shows the minimum references needed to run web tests in your project.

Assembly references for your web test project

Figure 1 – Assembly references for your web test project

With the references set up, we can get down to the business of writing some actual tests.

  • Add a new C# class file to your project called GoogleTests.cs.
  • Replace the default code in the new file with the following test to run a search for Carmina Burana on Google and check that the search results page contains the name Carl Orff (the composer of Carmina Burana)
using System;
using MbUnit.Framework;
using WatiN.Core;

namespace WebTestDemo
{
  [TestFixture]
  public class GoogleTests
  {
    [Test]
    public void GoogleSearchTest()
    {
      using (var browser = new IE())
      {
        browser.GoTo("http://www.google.com");
        var queryTextField = browser.TextField(Find.ByName("q"));
        queryTextField.TypeText("Carmina Burana");
        var searchButton = browser.Button(Find.ByName("btnG"));
        searchButton.Click();
        Assert.Contains(browser.Text, "Carl Orff");
      }
    }
  }
}

If you compile this code now, you may get the following compile-time warning:

warning CS1762: A reference was created to embedded interop assembly 'Interop.SHDocVw.dll' because of an indirect reference to that assembly created by assembly 'WatiN.Core.dll'. Consider changing the 'Embed Interop Types' property on either assembly.

To fix this, you will need to do the following.

  • Open the references folder for your project in the Solution Explorer window
  • Right-click on Interop.SHDocVw.dll and select Properties
  • When the Properties dialog appears, set the Embed Interop Types property to false

You can find slightly more information about this warning online at http://msdn.microsoft.com/en-us/library/ff183282.aspx.

Now you can compile the solution, you should run the test with any of your favourite test runners (e.g. TestDriven.Net, Echo, or Icarus). If everything goes well, you should see a new instance of Internet Explorer popping up during the execution of the test. WatiN should then interact with the text box and the search button and finally, the Google results page should appear with the expected search results. The test should be shown as passed. Figure 2 shows the test log created running this test in TestDriven.Net.

The test log

Figure 2 – The test log.

Refactoring for Readability

While the sample test method we’ve just run is neither long nor difficult to understand, there are a couple of areas which could be easier to read and which could easily suffer from coder error when repeated again and again over a number of tests:

  • It exposes unnecessary details about how to find certain HTML elements in the web page. The test needs to reference the search button and query textbox but we should be able to abstract away that the text box’s name is “q” and the button’s name is “btnG”.
  • It contains details about the initialization of the web browser. The IDisposable pattern that WatiN provides around its browser façade is quite nice, but it’s still too verbose and adds an extra indentation level that negatively affects the readability of the code, and thus obfuscates the intentions of the test.

Ideally, our test method should more look like the following.

[Test]
public void BetterGoogleSearchTest()
{
  var page = GoToPage();
  page.QueryTextField.TypeText("Carmina Burana");
  page.SearchButton.Click();
  Assert.Contains(Browser.Text, "Carl Orff");
}

Indeed, if we make fuller use of some features in both WatiN and MbUnit, we can pretty close to this ideal.

Using The WatiN Page Pattern To Hide Page Details

The first issue with the test that we highlighted above is that it exposes too many unnecessary details about the structure of the HTML document. These should be refactored away from the test code so that its intent is made clear and simple to read while the actual implementation is dealt with elsewhere. WatiN provides the Page pattern for this specific purpose. To quote the WatiN website,

When you create tests for a moderate to large website you quickly need a way to structure your test code. The Page pattern, natively supported by WatiN, does just that.

In a nutshell, the Page pattern means moving the code logic that interacts with the DOM elements into a separate class that derives from WatiN.Core.Page - an abstraction of the current web page. It exposes the elements we need to deal with and hides from the test how they are retrieved.

WatiN provides several parallel API to declare the elements we want to expose. You can use the imperative API and exposes the elements through properties and methods:

public TextField QueryTextField
{
  get { return Document.TextField(Find.ByName("q")); }
}

public Button SearchButton
{
  get { return Document.Button(Find.ByName("btnG")); }
}

Or you can use the declarative API which usually gives more concise code. It’s also acceptable to use this syntax on public fields.

[FindBy(Name = "q")]
public TextField QueryTextField;

[FindBy(Name = "btnG")]
public Button SearchButton;

The declarative API is perhaps best used only for elements which are well-identified in the HTML document. The imperative API meanwhile allows you to bind elements deeply buried in the document (e.g. an unnamed cell in an HTML table).

After refactoring, our search page test code now looks like this. Note that the Page class is nested into the TestFixture class. In general, you’ll only be testing one page per fixture class so it makes sense to treat the Page class as an implementation detail of the tests. However it’s perfectly valid to declare the page class as a first-class citizen type as well; especially if you need to reuse that model across several other test fixtures.

[TestFixture]
public class GoogleTests
{

  [Test]
  public void GoogleSearchTest()
  {
    using (var browser = new IE())
    {
      browser.GoTo("http://www.google.com");
      var page = browser.Page<GoogleSearchPage>();
      page.QueryTextField.TypeText("Carmina Burana");
      page.SearchButton.Click();
      Assert.Contains(browser.Text, "Carl Orff");
    }
  }

  public class GoogleSearchPage : WatiN.Core.Page
  {
    [FindBy(Name="q")]
    public TextField QueryTextField;

    [FindBy(Name = "btnG")]
    public Button SearchButton;
  }
}

Using the Gallio Extensibility Model to Automate the Browser Lifecycle

Our simple test no longer contains page-level details, which makes it slightly easier to read, but it still contains details about the initialization of the web browser. By default, WatiN provides control over IE or Firefox with an IDisposable-derived class that encapsulates the current instance of the browser. You simply declare the browser to be used in the test by wrapping it in a using statement which then automatically disposes the object and closes the browser window at the end of the test.

using (var browser = new IE())
{
  // Do something interesting here...
}

or

using (var browser = new Firefox())
{
  // Do something interesting here...
}

As noted earlier, this is a perfectly fine solution but it does mean unnecessary code indentations and noisy brackets that negatively affects the readability of the code, and thus obfuscates the intentions of the test.

To refactor this away, we’ll use code from one of the samples provided by the Gallio team: The Web Testing with MbUnit and WatiN sample code project on GitHub. If you have installed Gallio, you’ll find a link to it under Gallio > Samples in the Start Menu. The sample contains a project named “MbUnit.Web” which provides a few interesting helper classes for writing more efficient web tests.

AbstractBrowserTestFixture<TPage> is an abstract generic base class to derive your test fixture from, when you need to use the WatiN features. It’s a very simple class whose primary purpose is to manage the life cycle of the browser. That task is in fact done by an inner BrowserContext class which basically relies on a SetUp and Teardown method to create and kill the browser session. Deriving the test fixture from this abstract type allows you to skip the management of the browser lifespan. Here is the general pattern to follow:

public class SampleFixture : AbstractBrowserTestFixture<MyFixture.Page>
{
  [Test, RunBrowser]
  public void MyTest()
  {
    var page = Browser.GoToPage();
    // Interacts on the page and make assertions here...
  }

  [Url(“http://www.google.com”)]
  public Class MyPage : WatiN.Core.Page
  {
    // Expose DOM elements here.
  }
}

Some interesting points to note:

  • The test fixture derives from the generic abstract base class. The generic parameter is the type of the model of the web page you want to test.
  • The page class is decorated with an [Url] attribute which tells the browser context where to find the web page.
  • The test fixture most probably contains several test cases. Some of them are truly web tests, while some other may only be regular unit tests and you don’t want to start a browser session when they run. Only the test methods marked with the [RunBrowser] attribute with actually cause a browser session to be launched. This attribute also accepts an optional parameter to specify which browser you want to start (IE by default). Under the hood, that attribute attaches operations performed by the browser context to setup and teardown actions.

Let’s add MbUnit.Web into the project and refactor our test.

  • Download the latest version of the Web Testing with MbUnit and WatiN sample code project on GitHub.
  • In Visual Studio with the WebTestDemo project open, select File > Add > Existing Project
  • Select the MbUnit.Web.csproj file from the MbUnit.Web folder in the newly downloaded sample code.
  • When the MbUnit.Web project appears in Visual Studio’s Solution Explorer, right click the WebTestDemo’s References folder and add a reference to the MbUnit.Web project.

Using the above pattern, the code in GoogleTests.cs now looks like this.

using System;
using WatiN.Core;
using MbUnit.Web;
using MbUnit.Framework;

namespace WebTestDemo
{
  [TestFixture]
  public class GoogleTests : AbstractBrowserTestFixture
  {
    [Test, RunBrowser]
    public void GoogleSearchTest()
    {
      var page = GoToPage();
      page.QueryTextField.TypeText("Carmina Burana");
      page.SearchButton.Click();
      Assert.Contains(Browser.Text, "Carl Orff");
    }

    [Url("http://www.google.com")]
    public class GoogleSearchPage : WatiN.Core.Page
    {
      [FindBy(Name="q")]
      public TextField QueryTextField;

      [FindBy(Name = "btnG")]
      public Button SearchButton;
    }
  }
}

Far more legible, and if you compile and run the test, exactly the same effect.

Summary

In this article, we’ve written a simple web test using MbUnit and WatiN. We’ve identified various code smells which mean that while the test works as intended, it is in need of refactoring for both code clarity and ease of code reuse for future tests against this web page. We’ve looked at two possibilities:

  • Using WatiN’s Page class to abstract away the construction of the HTML page being tested.
  • Using MbUnit’s AbstractBrowserTestFixture class to abstract away the control of the browser lifecycle.

Next time, we’ll look at some more of the MbUnit.Web classes used for testing against the Visual Studio Development Server and for testing AJAX-enabled web pages.

You might also like...

Comments

About the author

Yann Trevin

Yann Trevin Luxembourg

Yann is a software engineer working at the research center of a large tire manufacturing company. He's mostly involved in web, desktop and mobile enterprise projects built on the .NET stack. He ...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“In theory, theory and practice are the same. In practice, they're not.”