codeflood logo

Automated Testing and Sitecore - Part 5

In the last part of this series we started to write tests around code which uses the Sitecore API. These tests were run inside the NUnit custom test runner we wrote back in part 3 of this series. In this post, we'll start testing our presentation components, starting with the easiest to test, which is a Sitecore WebControl. This is any class which inherits from Sitecore.Web.UI.WebControl.

The reason this type of control is so easy to test is because of the magical RenderAsText method which, as it's name would suggest, renders the control and returns the string output. This means we don't have to host the control in another container. We just have to instantiate an instance and start testing directly against it.

So let's provide some context to this example. We'll start by creating a simple Sitecore WebControl which just renders the current context item's summary field in a div, with an optional CSS class. There will be a property to set the CSS class to apply. As always, we'll develop this in a TDD style, so let's start writing some tests!

We'll create some test items in our test fixture setup method.

using NUnit.Framework;
using Sitecore.Collections;
using Sitecore.Data;
using Sitecore.Data.Items;

namespace Testing
{
  [TestFixture]
  public class WebControlTest
  {
    const string ITEM1_SUMMARY = "Item 1 has a summary";
    const string ITEM2_SUMMARY = "My summary";

    [TestFixtureSetUp]
    public void TestFixtureSetUp()
    {
      Database db = Sitecore.Context.Database;
      Item home = db.GetItem("/sitecore/content/home");
      using(new Sitecore.SecurityModel.SecurityDisabler())
      {
        Item item1 = home.Add("Item1", db.Templates["sample/sample item"]);
        using (new EditContext(item1))
        {
          item1["summary"] = ITEM1_SUMMARY;
        }

        Item item2 = home.Add("Item2", db.Templates["sample/sample item"]);
        using (new EditContext(item2))
        {
          item2["summary"] = ITEM2_SUMMARY;
        }
      }
    }

    [TestFixtureTearDown]
    public void TestFixtureTearDown()
    {
      Database db = Sitecore.Context.Database;
      Item home = db.GetItem("/sitecore/content/home");
      ChildList children = home.GetChildren();
      using (new Sitecore.SecurityModel.SecurityDisabler())
      {
        if (children["item1"] != null)
          children["item1"].Delete();
        if (children["item2"] != null)
          children["item2"].Delete();
      }
    }
  }
}

The complexity of the control will determine how you assert based on the string output of the control. For a simple control you'll probably just assert on string comparisons or existence of substrings in the output. If the control is more complex, I would suggest HtmlAgilityPack. I'll demonstrate both in these examples.

First, a simple string comparison test.

[Test]
public void Item1()
{
  Sitecore.Context.Item =
    Sitecore.Context.Database.GetItem("/sitecore/content/home/item1");
  MyControl control = new MyControl();
  string output = control.RenderAsText();
  Assert.AreEqual("<div>" + ITEM1_SUMMARY + "</div>", output);
}

We could also use a regular expression to account for whitespace either side of the text inside the div (new line, tabbing, etc). In this case our assertion would look like:

Assert.IsTrue(Regex.IsMatch(output, @"\s*<div>\s*" + ITEM1_SUMMARY + @"\s*</div>\s*"));

Now, we'll adjust the CSS class property and use HtmlAgilityPack to assert the output structure.

[Test]
public void Item1WithClass()
{
  Sitecore.Context.Item = 
    Sitecore.Context.Database.GetItem("/sitecore/content/home/item1");
  MyControl control = new MyControl();
  control.DivClass = "myclass";
  string output = control.RenderAsText();

  HtmlDocument doc = new HtmlDocument();
  doc.LoadHtml(output);
  XPathNavigator nav = doc.CreateNavigator();

  XPathNodeIterator div = nav.Select("//div");
  Assert.AreEqual(1, div.Count);
  div.MoveNext();

  Assert.AreEqual("myclass", div.Current.GetAttribute("class", string.Empty));
  Assert.AreEqual(ITEM1_SUMMARY, div.Current.Value.Trim());
}

You might be looking at the code example above and saying to yourself "Well, string comparisons are heaps easier and require a lot less code". The benefit of using HtmlAgilityPack is it's resilience to whitespace. It you were doing string comparisons or regex you would have to take into account <div>content</div> and <div> content </div> and \\t\\t\\t<div>\\r\\ncontent\\r\\n</div>. But HtmlAgilityPack hides you from these nasties and will handle small changes in your code better. For example, I may have generated the above just using string concatenation in my code:

string output = "<div>" + item["summary"] + "</div>";

Then I want to add some attributes and it starts to get ugly. So I do what I should have from the start, and I'll start using the HtmlTextWriter and the output stacks, which will do this real easy for me:

output.AddAttribute(HtmlTextWriterAttribute.Class, DivClass);
output.RenderBeginTag(HtmlTextWriterTag.Div);
output.Write(item["summary"]);
output.RenderEndTag();

But the side effect of using the HtmlTextWriter is that it will also indent your markup properly. So output of:

<div>Summary</div>

Just changed to:

<div>
  Summary
</div>

HtmlAgilityPack will handle that change for you, but string comparisons will require tweaks.

We also need to write some more tests to cover other cases such as the item not having a summary, the item being null, the summary being too long, too short, text too long, too short, etc, etc.

After we've written our tests, it's time to write our control.

public class MyControl : Sitecore.Web.UI.WebControl
{
  public string DivClass
  {
    get;
    set;
  }

  protected override void DoRender(HtmlTextWriter output)
  {
    if (!string.IsNullOrEmpty(DivClass))
      output.AddAttribute(HtmlTextWriterAttribute.Class, DivClass);

    output.RenderBeginTag(HtmlTextWriterTag.Div);
    output.Write(Sitecore.Context.Item["summary"]);
    output.RenderEndTag();
  }
}

And now we have a fully tested Sitecore WebControl.

In the next installment of this series we'll cover how to test our static output presentation components such as layouts, sublayouts and renderings.

Comments

I've just taken over development of Html Agility Pack (after years of neglect). I'm looking for input on it. I'm also trying to get in contact with lead developers at sitecore, so far all my emails have been unanswered. I came across a few posts that say SiteCore includes HAP with it's distribution and am guessing some of their devs probably have wishlists and/or updates for it.
I was wondering if you had some feedback and also by chance an inside line to some of their developers.

Hi Jeff,
I am one of the founders of Sitecore, and VP Technical Marketing. As you are requesting contact with Sitecore, Alistair has been so kind to give me the mail you registered your post under (gmail).
I have sent an answer to this address.

[...] Automated Testing and Sitecore part 5 &#8211; Testing WebControls [...]

Leave a comment

All fields are required.