Unit Testing in Sitecore using Nunit with Sitecore Context information

Few months back, we were doing R&D to implement Unit Testing for our Sitecore project. We tried different kind of solutions like Miscosoft’s Unit Testing Template which we can create from Visual Studio directly and Nunit for which you can install Nuget Package from Visual Studio.

Both has all the functionality that we require in Unit Testing with some different gesture. Microsoft’s Unit Testing project can easily be run from Visual Studio itself and there are couple of ways to run Nunit either by command line or using GUI. I would not go into detail of implementing it.

What is the challenge?

Everything works well in either of two solutions until Sitecore Context comes into the picture. In our case, all the module were tightly coupled so it was not properly unit testable without Sitecore Context information. And we were also needed to perform testing on live items so Fake DB couldn’t help. So, we created Unit Test project using NUnit and executed these unit tests using a tool page from inside Sitecore website with Sitecore Context information. Please see implementation below:

Step 1: Creating Unit Test peoject using NUnit

  • Create a Class Library project, we will call it “MyProject.Tests” here.
  • Add reference of “Sitecore.Kernal.dll” to the project.
  • Install NUnit from Nuget package manager console or download latest NUnit asemplys from download page.
  • Add references of “nunit.core.dll”, “nunit.core.interfaces.dll”, and “nunit.framework.dll” to the project.
  • Create a class and tests as shown in below code:
using NUnit.Framework;
namespace MyProject.Tests
{
    [TestFixture, Category("TestGroup1")]
    public class TestMethods
    {
        [Test]
        public void Test1()
        {
            //Use Sitecore Context information here. It really works!
            String ContextSite = Sitecore.Context.Site.Name;
            String ContextItemName = Sitecore.Context.Item.Name;
            Assert.IsNotNullOrEmpty(ContextItemName, "Cannot access Sitecore Context information.");
        }
    }
}

Step 2: Create aspx page into Sitecore website to execute Tests manually

After created Unit Test project, It’s turn to execute these tests. In place of traditional way of executing either from command line or from GUI of NUnit we will execute it using code from “.aspx” page within Sitecore website.

  • Create an “.aspx” page in your sitecore website, we will call it “ExecuteUnitTest.aspx” here.
  • Add references of “nunit.core.dll”, “nunit.core.interfaces.dll”, and “nunit.framework.dll” to the website as well.
using NUnit.Core;
using NUnit.Core.Filters;
Inherit NUnit.Core.EventListner to the page.
public partial class ExecuteUnitTest : System.Web.UI.Page, EventListener
namespace website.tools
{
    public partial class ExecuteUnitTest : System.Web.UI.Page, EventListener
    {
        #region Member Variables
        DataTable m_results = new DataTable();
        private int m_executedCount = 0;
        private int m_failedCount = 0;
        private TestSuite m_testSuite = null;
        #endregion

        protected void Page_Load(object sender, EventArgs e)
        {
            // Initialise data table to hold test results
            m_results.Columns.Add("test");
            m_results.Columns.Add("result");
            m_results.Columns.Add("message");
            m_results.Columns.Add("class");

            // Initialise controls
            lblResult.Text = "";
            ltlStats.Text = "";

            // Initialise NUnit
            CoreExtensions.Host.InitializeService();

            // Find tests in current assembly
            TestPackage package = new TestPackage(Server.MapPath("~/bin/MyProject.Tests.dll")); // PATH TO THE DLL OF THE UNIT TEST PROJECT
            m_testSuite = new TestSuiteBuilder().Build(package);

            if (!IsPostBack)
            {
                // Display category filters
                StringCollection coll = new StringCollection();
                GetCategories((TestSuite)m_testSuite, coll);
                string[] cats = new string[coll.Count];
                coll.CopyTo(cats, 0);
                Array.Sort(cats);
                cblCategories.DataSource = cats;
                cblCategories.DataBind();
            }
        }

        protected void RunClick(object sender, EventArgs args)
        {
            // Determine if any category filters have been selected
            StringCollection categories = new StringCollection();
            for (int i = 0; i < cblCategories.Items.Count; i++)             {                 if (cblCategories.Items[i].Selected)                     categories.Add(cblCategories.Items[i].Value);             }             string[] arCats = new string[categories.Count];             categories.CopyTo(arCats, 0);             // Create a category filter             ITestFilter filter = new CategoryFilter(arCats);             //ITestFilter filter = new NUnit.Core.Filters.SimpleNameFilter("GetExternalContentAndTransform");             TestResult result = null;             // Run test suite with appropriate filter             if (arCats.Length >= 1)
                result = m_testSuite.Run(this, filter);
            else
                result = m_testSuite.RunSuite(this, NUnit.Core.Filters.SimpleNameFilter.Empty);
            //result = m_testSuite.Run(this, NUnit.Core.Filters.SimpleNameFilter.Empty);

            // Bind results to presentation
            DataView m_resultsview = new DataView(m_results);
            DataTable dt = m_resultsview.ToTable(true, "test");
            gvResults.DataSource = m_results;
            gvResults.DataBind();

            // Display statistics
            ltlStats.Text = string.Format("{0} out of {1} tests run in {2} seconds.", m_executedCount, result.Test.TestCount, result.Time);

            if (m_failedCount > 0)
                ltlStats.Text += string.Format("
{0} {1} failed", m_failedCount, m_failedCount == 1 ? "test" : "tests");

            int skipped = result.Test.TestCount - m_executedCount;
            if (skipped > 0)
                ltlStats.Text += string.Format("
{0} {1} skipped", skipped, skipped == 1 ? "test" : "tests");

            lblResult.Text = "Suite " + (result.IsSuccess ? "Passed" : "Failed");
            if (result.IsSuccess)
                lblResult.CssClass = "passLabel";
            else
                lblResult.CssClass = "failLabel";
        }

        ///
<summary>
        /// Find all available categories in the test suite
        /// </summary>

        /// <param name="suite">The test suite to get categories from</param>
        /// <param name="cats">Output collection containing categories found</param>
        private void GetCategories(TestSuite suite, StringCollection cats)
        {
            if (suite.Categories != null)
            {
                for (int i = 0; i < suite.Categories.Count; i++)
                    if (!cats.Contains((string)suite.Categories[i]))
                        cats.Add((string)suite.Categories[i]);
            }

            for (int i = 0; i < suite.Tests.Count; i++)
                if (((ITest)suite.Tests[i]).IsSuite)
                    GetCategories((TestSuite)suite.Tests[i], cats);
        }

        #region EventListener Members

        public void TestFinished(TestResult result)
        {
            // Put results into data table
            DataRow dr = m_results.NewRow();
            dr["test"] = result.Test.TestName;
            dr["message"] = result.Message;
            if (result.IsFailure)
                dr["message"] += result.StackTrace;
            dr["class"] = "notRun";

            if (result.IsSuccess && result.Executed)
            {
                dr["result"] = "Pass";
                dr["class"] = "pass";
            }

            if (result.IsFailure && result.Executed)
            {
                dr["result"] = "Fail";
                dr["class"] = "fail";
                m_failedCount++;
            }

            if (result.Executed)
                m_executedCount++;

            m_results.Rows.Add(dr);
        }

        public void RunFinished(Exception exception)
        {
        }

        public void RunFinished(TestResult result)
        {
        }

        public void RunStarted(string name, int testCount)
        {
        }

        public void SuiteFinished(TestResult result)
        {
        }

        public void SuiteStarted(TestName testName)
        {
        }

        public void TestOutput(TestOutput testOutput)
        {
        }

        public void TestStarted(TestName testName)
        {
        }

        public void UnhandledException(Exception exception)
        {
        }

        #endregion
    }
}

When you view above page in the browser using link http://#MySitecoreDomain#/tools/#ExecuteUnitTest.aspx#,
You will see all the categories specified in the Unit Testing project. Upon selecting one or more
selecting when you click submit, It will run all the test suites available within that category
and displays the result of the tests as shown in below image.

UnitTest1
After executing suite, it looks like below:

UnitTest2
And thus way you can write the tests which is highly dependent on Sitecore Context information.

Enjoy Unit Testing with Sitecore Context. No Fake 🙂

Advertisements