man thinking

Unit Testing a Web Part

Building tests for UI components is one of the more difficult challenges in trying to do automated unit testing. You might wonder if there was any hope at all in finding a way of unit testing a web part in an automated way, especially when you consider that in the case of web part development the trivial example web part is in many cases not that far from the reality for a lot of production code. With such a simple component to test, it is difficult to justify some of the more elaborate solutions to unit testing such as using Model-View-Controller/Presenter patterns.

Let us suppose that we have a requirement for a web part that renders a SharePoint list as an HTML un-ordered list element (and for the sake of the example we will suppose that there is not an existing out-of-the-box SharePoint web part that can do this). Here is how we might implement that WebPart class:

using System.Web.UI.WebControls.WebParts;
using System.Web.UI;
using Microsoft.SharePoint;

namespace ClassLibrary1
{
  public class WebPart1 : WebPart
  {
    [WebBrowsable(), WebDescription("Name of list containing items")]
    public string ListName { get; set; }

    protected override void CreateChildControls()
    {
      SPList list = SPContext.Current.Web.Lists[ListName];
      if (list.ItemCount>0) Controls.Add(new LiteralControl("<ul>\r\n"));
      foreach (SPListItem item in list.Items)
        Controls.Add(new LiteralControl("  <li>" + item.Title + "\r\n"));
      if (list.ItemCount > 0) Controls.Add(new LiteralControl("</ul>\r\n"));
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
      writer.WriteLine("<h2>Contents of List:</h2>");
      base.RenderContents(writer);
    }
  }
}

We have included output in both the RenderContents and the CreateChildControls methods for illustration – normally it would be one or the other. The question is; how to test the output? One approach that has been suggested is to bring the logic inside a method that does not depend on the user interface. For this to work the WebPart would have to be rewritten to be something like the following:

using System.Web.UI.WebControls.WebParts;
using System.Web.UI;
using Microsoft.SharePoint;

namespace ClassLibrary1
{
  public class WebPart1 : WebPart
  {
    [WebBrowsable(), WebDescription("Name of list containing items")]
    public string ListName { get; set; }

    public string CreateList()
    {
      System.Text.StringBuilder content = new System.Text.StringBuilder();
      SPList list = SPContext.Current.Web.Lists[ListName];
      if (list.ItemCount > 0) content.AppendLine("<ul>");
      foreach (SPListItem item in list.Items)
        content.AppendLine("  <li>" + item.Title + "</li>");
      if (list.ItemCount > 0) content.AppendLine("</ul>");
      return content.ToString();
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
      writer.WriteLine("<h2>Contents of List:</h2>");
      writer.Write(CreateList());
      base.RenderContents(writer);
    }
  }
}

Now that we have the important part of the web part in a public method we can build a test class that will call it. Assuming you are using a professional version of Visual Studio this is most easily achieved using the built-in MSTest unit testing framework. First you need to create a new test project in your solution. By default the WebPart will be added as a reference in the test project, but I prefer the approach of including the source file for the WebPart in the test project as a linked file for reasons that will become clear later. The example below shows the resulting simplified tests class:

using ClassLibrary1;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestProject
{
  [TestClass]
  public class WebPart1Test
  {    
    [TestMethod]
    public void CreatesUnorderedList()
    {
      WebPart1 webpart = new WebPart1();
      webpart.ListName = "Tasks";
      string result = webpart.CreateList();
      string expected = @"<ul>
  <li>Build a set of test cases</li>
  <li>Implement code to satisfy test cases</li>
</ul>
";
      Assert.AreEqual(result, expected);
    }
  }
}

The same results can of course be achieved using other testing frameworks. For example, NUnit can be used by including the NUnit framework in the project references, changing the TestClass and TestMethod attributes to TestFixture and Test respectively, and modifying the Assert statement.

The problem with this approach is that it requires us to design the WebPart for the purpose of the test class. Enthusiasts for Test-Driven Development (TDD) often assert that whenever code has to be written in a particular way in order to make it testable, that this is somehow the 'right' way to code, and to do it any other way would be bad programming practice. Another issue is that there is still a dependency on the SharePoint object model in the CreateList method, including a reference to the SPContext object - more on this later. The way a WebPart is constructed is pretty much fixed by the ASP.net framework and it would be nice if we could make automated unit testing work without having to re-invent it, or introduce extra layers of classes and interfaces. What we would really like to do is call the RenderContents method of the WebPart directly and examine the returned mark-up.

Herein lies the first problem. We cannot simply create an HTML writer object and pass it to the RenderContents method:

  HtmlTextWriter writer = new HtmlTextWriter(); 
  Webpart.RenderContents(writer); 

This is because RenderContents is a protected member of the WebPart class. One solution to this is to use the built-in ability of Visual Studio to generate an accessor class to the WebPart. This happens automatically if you right-click on the WebPart class itself in visual studio and select the option to generate a test. This does solve the problem but it is relies on taking the output of the WebPart project as a dependency in the test project. This prevents us from later building the WebPart with different options or against different versions of dependencies. It can also give problems later when we start refactoring code as the generated accessor classes can get 'out of sync'.

Because the method in question is protected, rather than private, another option is available to us; we can derive a new class from the WebPart class and add accessor methods that are public, thus avoiding the need to modify the WebPart class by adding test code. However, an even easier way is to simply derive the test class itself from the WebPart. I think this is quite an elegant solution and means that a test class can be created which gives full test coverage of the WebPart without needing any changes to the WebPart itself. The resulting test class is as follows (the WebPart under test can be either of the versions above):

using ClassLibrary1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Web.UI;
using System.IO;

namespace TestProject2
{
  [TestClass]
  public class WebPart1Test : WebPart1
  {    
    [TestMethod]
    public void RenderContents_Tasks()
    {
      ListName = "Tasks";
      StringWriter sw = new StringWriter();
      HtmlTextWriter writer = new HtmlTextWriter(sw);
      EnsureChildControls();
      RenderContents(writer);
      string expected = @"<h2>List Contents:</h2>
<ul>
  <li>Build a set of test cases</li>
  <li>Implement code to satisfy test cases</li>
</ul>
";
      Assert.AreEqual(sw.ToString(), expected);
    }
  }
}

The call to EnsureChildControls is necessary to make the base class calls the CreateChildControls method, in case there is an override in the derived class. This test works regardless of whether the web part content is rendered by overriding the RenderContents method or the CreateChildControls method. It is also possible to use ASP.NET web controls in building the HTML content. The important thing here is that it has not been necessary to change the code of the web part itself in order to test it.

Writing our test code this way avoids the need to modify the web part code, so this is also a good way to add tests to web parts that have already been written. There is no need to re-architect the web part to use the MVP pattern, for example. We still need to supply an SPContext for this to work - we will cover this in another article.

Obviously you can still use an MVP or MVVP pattern for your web part if you want to, and continue to use this approach to testing. But I really would only recommend doing this if the complexity and nature of your web part justifies it. You need to be sure that using these patterns adds something - are all the extra classes, interfaces for view and presenter and separate classes for two or three components, doing anything other than adding extra source code. In some cases this may be appropriate where there is substantial logic in these three areas and there is a genuine need to separate these 'concerns'. But I am of the view that the architecture should not be dictated by the testing methodology. We don't want to generate a lot of code that does nothing, or tests that test nothing.