New Technique for Unit Testing Renderings in Sitecore
Unit testing in Sitecore always seems to be a hot topic amongst the Sitecore community, especially the new comers to the Sitecore game. And rightly so. Working out how you can effectively unit test your Sitecore code can be very confusing when you’re still trying to come to grips with a new system and it’s way of doing things. And it’s not immediately apparent how you can do such testing. I think the biggest key to being able to write effective unit tests with Sitecore is to give up the notion of proper unit testing. Roy Osherove (unit testing guru) recently wrote what I think is the best and most concise definition of what a unit test is and what it should do:
A unit test is a fast, in-memory, consistent, automated and repeatable test of a functional unit-of-work in the system.
Given the above definition we can easily see that almost any test we write for code which uses the Sitecore API cannot be a unit test, especially if we’re using any data (Items) from Sitecore as this API will pull the data from an external database engine. So the biggest key to writing effective tests with Sitecore is to call this subject automated testing, not unit testing. We can’t isolate the test to a point where the entire thing runs in memory, so don’t worry about trying. It’s more important to have an automated, reliable, repeatable suite of tests you can run at the click of a button. The other barrier a lot of people face when trying to write automated tests for code which uses the Sitecore API is trying to have your tests run outside of ASP.NET. Sitecore is an ASP.NET application. It relies on HttpContext
and the ASP.NET request lifecycle and rendering engine not to mention the giant configuration file required as well. I’ve never actually heard of anyone successfully running Sitecore API code outside of ASP.NET (let me know if you have). This is a huge barrier to a lot of people who think their tests have to run inside their chosen unit testing framework’s test runner. This is not the case. The easiest, quickest and most reliable way around this is to simply have your tests run inside Sitecore. I’ve written and released an automated test runner for NUnit tests which can be used to run your own tests inside your Sitecore application. This is not an extraordinary circumstance. The SilverUnit project provides a unit testing framework inside of Silverlight. So don’t feel like this concept of running testing inside the application is too foreign. I wrote an 8 part series on automated testing with Sitecore a while back. The rundown of the series was as follows:
- Automated Testing and Sitecore part 1 - Introduction
- Automated Testing and Sitecore part 2 - Custom test runner
- Automated Testing and Sitecore part 3 - Test setup and tear down
- Automated Testing and Sitecore part 4 - Test code written against the Sitecore API
- Automated Testing and Sitecore part 5 - Testing WebControls
- Automated Testing and Sitecore part 6 - Testing static presentation components
- Automated Testing and Sitecore part 7 - Testing dynamic presentation components
- Automated Testing and Sitecore part 8 - Testing guidelines
Since writing this series I’ve discovered a new technique for testing your renderings. This has also revealed a short coming in the technique shown in part 5 above for testing WebControls. You’ll see why this is later on in this post. The basic process for this new technique is:
- Instantiate rendering in test code
- Set the context item to test rendering against
- Gather output and use HtmlAgilityPack to construct an XML document from it
- Verify your output
The above of course needs to run inside Sitecore, so I’ll use my custom test runner. A nuance of the current test runner, your test fixture needs to be attributed with a NUnit.Framework.CategoryAttribute
attribute. This is so the test runner can produce a list of categories to allow you to only test a small selection from your entire test suite for a particular test run. The tests run are those inside the selected categories.
Follow these steps to setup your test project:
- Grab yourself a copy of the NUnit libraries from http://www.nunit.org/ or simply use the NUnit libraries bundled with the item below.
- Download my custom NUnit test runner from http://www.codeflood.net/testrunner/.
- Create your test project and include a reference to the NUnit.Core, NUnit.Core.Interfaces and NUnit.Framework assemblies.
- Copy the 3 test runner source files (aspx, aspx.cs and designer.cs files) into your project folder and include in the project.
- Change the namespace of the test runner code behind and the titles in the aspx file.
The test runner has been written to run the tests in the same assembly as the runner, so you can write your tests in the same project. Now, let’s create a new test fixture that we can write tests for the rendering under inspection.
[TestFixture]
[Category("NavTest")]
public class NavTest
{
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
}
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
}
}
When testing a rendering you need to be sure of the items it’s reading, so we’ll need to create the test content items the rendering will be run against. These will be created in the TestFixtureSetUp
method. And of course this test suite would be bad if we didn’t return the environment back to the way we found it before we started our test, so we’ll delete the test items in the TestFixtureTearDown
method. The attributes on these classes tell NUnit to run TestFixtureSetUp
before any test is run (preparing the test environment) and run TestFixtureTearDown
after the tests have all completed (returning the test environment to it’s previous state).
private Item m_root, m_child1, m_child2, m_child3 = null;
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
var home = Sitecore.Context.Database.GetItem("/sitecore/content/home");
var template = Sitecore.Context.Database.Templates["User Defined/Page"];
using (new SecurityDisabler())
{
m_root = home.Add("root", template);
m_child1 = m_root.Add("Child 1", template);
m_child1.Editing.BeginEdit();
m_child1["title"] = "Child 1 Title";
m_child1.Editing.EndEdit();
m_child2 = m_root.Add("Child 2", template);
m_child3 = m_root.Add("Child 3", template);
}
}
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
using (new SecurityDisabler())
{
m_root.Delete();
}
}
I’ve stored the items in member variables so I can make my assertions against them. Now that we’ve got our test all setup we can start testing the rendering. The rendering I’ll be testing in the below code is a basic child navigation control. The text of each link should be taken from the title
field of the item. If that field is empty, then the name should be used. So I’ll be testing 2 conditions. The first is the inclusion of the correct items in the navigation (the children), and the second is the logic around the text of the link. In the above setup code I’ve set a title on child 1 which is different to it’s name, so I can test the link text logic using that item. And so onto the initial rendering.
<xsl:template match="*" mode="main">
<ul>
<xsl:for-each select="child::item">
<li>
<sc:link/>
</li>
</xsl:for-each>
</ul>
</xsl:template>
You’ll notice the above XSLT code contains an error in the way it generates the text of the link. The sc:link
tag will use the name of the item for the link text. This way we get to see our test fail the first time so we know it’s actually working. Onto some tests to verify our rendering. The first will be for item 1 where I’m testing it’s inclusion and the link text.
[Test]
public void InclusionAndTitle()
{
var xslFile = new XslFile();
xslFile.Path = "xsl/Nav.xslt";
Sitecore.Context.Item = m_root;
var output = xslFile.RenderAsText();
var doc = new HtmlDocument();
doc.LoadHtml(output);
var url = LinkManager.GetItemUrl(m_child1);
var nav = doc.CreateNavigator().SelectSingleNode(string.Format("//a[@href='{0}']", url));
var linkText = nav.Value;
Assert.AreEqual("Child 1 Title", linkText);
}
For tests 2 and 3 I’ll pretty much just be replicating this same code, so I’ll refactor it so I can write less code.
[Test]
public void InclusionAndTitle()
{
var linkText = GetLinkText(m_root, m_child1);
Assert.AreEqual("Child 1 Title", linkText);
}
[Test]
public void InclusionAndName()
{
var linkText = GetLinkText(m_root, m_child2);
Assert.AreEqual("Child 2", linkText);
}
[Test]
public void InclusionAndName2()
{
var linkText = GetLinkText(m_root, m_child3);
Assert.AreEqual("Child 3", linkText);
}
private string GetLinkText(Item renderContextItem, Item textItem)
{
var xslFile = new XslFile();
xslFile.Path = "xsl/Nav.xslt";
Sitecore.Context.Item = renderContextItem;
var output = xslFile.RenderAsText();
var doc = new HtmlDocument();
doc.LoadHtml(output);
var url = LinkManager.GetItemUrl(textItem);
var nav = doc.CreateNavigator().SelectSingleNode(string.Format("//a[@href='{0}']", url));
return nav.Value;
}
Running the above tests I would expect test 1 would fail as the text is not as expected. Now we can rework the XSLT code and correct the bug.
<xsl:template match="*" mode="main">
<ul>
<xsl:for-each select="child::item">
<li>
<sc:link/>
<xsl:apply-templates mode="renderTitle" select="."/>
</sc:link>
</li>
</xsl:for-each>
</ul>
</xsl:template>
<xsl:template match="item" mode="renderTitle">
<xsl:choose>
<xsl:when test="sc:fld('title',.) != ''">
<sc:text field="title"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@name"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Now if you’ve inspected the test code above in the GetLinkText
method you might be thinking you could adjust the code and test sublayouts and Sitecore web controls using this same technique. Well, you might be able to. Kind of. One thing to note about how the code is tested is that the normal ASP.NET request lifecycle is not followed. We are actually half way through the request lifecycle inside the test runner which is run inside the ASP.NET application so it’s invoked through an HTTP request. This just means that some things don’t run. For example, I was successful using this technique for testing a Sitecore web control, but only the DoRender
method is called during the test. All the other events such as OnLoad
don’t get called. So as long as your Sitecore web control doesn’t require the events to be fired then you’ve got a very good chance of using this technique for testing. Below is an example of gathering the output of a Sitecore web control.
var c = new Banner();
Sitecore.Context.Item = m_grandgrandchild;
var output = c.RenderAsText();
This code is much simpler than that for a rendering cause we can instantiate the web control directly rather than going through another level of indirection as we do with a rendering. I was much less successful testing sublayouts. Although the Sitecore API calls worked, when using a template control like a repeater, the repeater didn’t render correctly. If your sublayout is very basic you might be able to use this technique, but you’ll also need to call the Expand
method on the sublayout instance before the call to RenderAsText
.
var sublayout = new Sublayout();
sublayout.Path = "layouts/list.ascx";
Sitecore.Context.Item = renderContextItem;
sublayout.Expand();
var output = sublayout.RenderAsText();
var doc = new HtmlDocument();
doc.LoadHtml(output);
So I hope this technique makes it easier for you to test your Sitecore presentation components.
Very very cool.
As always, a true pleasure to read your posts.