Insights

Complete Unit Testing In Sitecore With Sitecore.Context And App_Config

Feels Like Magic

Unit testing in Sitecore can be tricky. There are a lot of moving parts. And to truly be able to test, you need to somehow bring all of those parts into a test.

That's exactly what I'm attempting do.

  • Execute unit tests within a valid Sitecore context
  • Load all current App_Config/Include/ configs at runtime
  • Access to all APIs
  • Maintain to additional config files
  • No scripting involved

At high-level we'll accomplish this by:

  • Renaming your web.config to app.config
  • Including all config files in your IntegrationTests project as Links
  • Run a line of a voodoo code that dynamically sets Sitecore's current directory

Let's do this. It's mostly screenshots and code samples anyways.

Create A New Test Project

Create a new project in Visual Studio for the tests. Make sure it's a standard Class Library. Call it what you want, I like Fishtank.IntegrationTests.

Create Your App.Config

Steps:

  • Copy & paste your web.config from your website into IntegrationTests project.
  • Rename it app.config.
  • Check the app.config's properties (alt+enter). Ensure that Copy to Output Directory is set to Copy Always.

A Class Library uses an app.config instead of a web.config. Hopefully, you're following best-practices and modifying your web.config indirectly via /App_Config/Include/

As rule, we want to make sure every config we drop in here is set to Copy.

Recreate The App_Config Folder Structure

We'll create a folder structure inside Fishtank.IntegrationTests, mimicking \Website\App_Config:

- App_Config
    - Include
    - Prefetch
    - Security

Add Config Files As A Link

Now here comes a wee bit of magic. We need all of our config files in our IntegrationTests project but don't want to maintain two copies of them. For this we use Add As Link:

  • Right-click App_Config inside to the IntegrationTests project
  • Add > Existing Item...
  • Navigate to file explorer to Website\App_Config
  • Use an asterisk to show all the config files.

  • Use Shift to highlight all files (not folders)
  • Note the down arrow beside "Add" > click > Select Add As Link

Repeat these step with the App_Config/Include, App_Config/Prefetch & App_Config/Security folders respectively.

Set All Linked Configs To Copy

One final step, super important. Highlight all the config files in each folder under App_Config change Copy to Output Directory setting to Copy if newer.

Be sure to Save All on the solution after doing this.

All Configs Set As Linked

Notice all files have been added. They have a special icon indicating that they're linked.

Add NUnit Via NuGet

NUnit is my test runner of choice. Feel free to divert from the step is you have your own favorite. Here are the steps.

  • In your Integration project, right-click References
  • Click Manage NuGet Packages...

  • Search for nunit using the search box. Add it.

  • Rock and roll.

Add Sitecore DLL References

In your Integration project, under References, add a local reference to Sitecore.Kernel.dll & Sitecore.Nexus.dll. Make sure Copy Local is set to True for in the properties.

Change Sitecore's Root Path During Tests

This step is bit of a dark art. We're make a class that executes before any test(s) are run. It reassigns Sitecore's file system to run from the IntegrationTests project build folder:

  • Create a new Class called TestSetupFixture.cs in the IntegrationTests project.

  • Copy & paste the below code.
  • See inline comments for a brief explanation

TestSetupFixture.cs


using System;
using System.IO;
using NUnit.Framework;
using Sitecore;
using Sitecore.Configuration;
using Sitecore.Globalization;
using Sitecore.SecurityModel;

namespace Fishtank.IntegrationTests
{
    // In NUnit, this ensures it will run before any/all tests are executed
    [SetUpFixture]
    public class TestSetupFixture
    {
        public static SecurityDisabler _disabler;


        [SetUp]
        public void SetupTest()
        {
            try
            {
                // This grounds Sitecore in the current directory so when 
                // Sitecore.IO.FileUtil.MapPath runs, it can find the files.                
                State.HttpRuntime.AppDomainAppPath = Directory.GetCurrentDirectory();

                // This static.  Allows it live, avoiding garbage collection
                _disabler = new SecurityDisabler();


                // If you need to run pipelines do it hear.
                //CorePipeline.Run("initialize", new PipelineArgs());

                // Set any properties you need in content
                Context.SetLanguage(Language.Parse("en"), false);
            }

            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }
        }
    }
}

If you don't use NUnit, you'll have to find an equivalent methodology. Or simply run the above code manually at the start of your tests.

Create A Unit Test To Prove It Works

Now we'll create a test to prove that we're up and running. Let's call it SimpleTests.cs. Decorate the class as a [TestFixture] and the test method with [Test].

This test will prove the following:

  • The configs /App_Config/Include are being processed. The information we're looking for is coming from /App_Config/Include/SiteDefinition.getfishtank.ca.config
  • The Sitecore Context does exist
  • Context-specific values can be set, retrieved
  • Databases are active

using NUnit.Framework;

namespace Fishtank.IntegrationTests
{
    [TestFixture]
    public class SimpleTests
    {
        [Test]
        public void ShouldFindTheHomeNode()
        {
            // Get getfishtank is declared under:
            // /App_Config/Include/SiteDefinition.getfishtank.ca.config
            Sitecore.Context.SetActiveSite("getfishtank");
                        
            // Pull the start path of the site
            string startPath = Sitecore.Context.Site.StartPath;
            
            // Pull the database name
            string databaseName = Sitecore.Context.Site.Database.Name;

            // Load the web database, and get item
            var db = Sitecore.Data.Database.GetDatabase("web");
            var item = db.GetItem("/sitecore/content/fishtank/home");           
           
            
            // Found the home node
            Assert.That(item, Is.Not.Null);

            // Paths of the home items match
            Assert.That(startPath.ToLower(), Is.EqualTo(item.Paths.FullPath.ToLower()));
            
            // Database name pulled from context matches too 
            Assert.That(databaseName, Is.EqualTo(db.Name));
        }
    }
}

Almost there!

Proof That It Worked

For proof, this is the best I can reasonably do.

An all encompassing screenshot showing:

  • Visual Studio
  • Open SimpleTest.cs test file
  • Open site defintion file, /App_Config/Include/SiteDefinition.getfishtank.ca.config
  • NUnit test runner window showing a passed test
  • Properties window showing that the linked config resides in /App_Config/Include/.

Potential Issues

If your file system paths in your web.config are absolute, they may need adjusting. Prefixing paths with a dot ./App_Config/ is all that's usually required. Definitely beware of the paths.

Also - be sure to Rebuild your IntegrationTests project in Visual Studio before you run your tests. The Rebuild copies your App_Config folder to the tests bin folder. For some reason, running tests using NUnit doesn't copy them.

If you this is your problem, you'll see these errors:

TestFixtureSetUp failed in TestSetupFixture
Exception doesn't have a stacktrace
Could not find a part of the path 'C:\getfishtank\Fishtank.IntegrationTests\bin\Debug\app_config\prototypes.config'.

Closing Thoughts

To recap fundamental principles here:

  • Renamed your web.config to app.config
  • Include all config files in your IntegrationTests project as Links
  • Run a line of a voodoo code that dynamically sets Sitecore's current directory

This looks like a bit of work but once you understand the steps, it's quick process. And being able to test with a context and all your configs is defintely worth the effort.

Technically, we could deliniate between integration tests and unit tests. Connecting to Sitecore makes this approach integration testing as opposed to unit testing. Unit testing has no external dependency. That distinction may or may not be relevant to you.

If you have any questions or comments please leave them below or reach me on twitter at @dancruickshank. I'm happy to help.

This post was authored using Markdown for Sitecore.

👋 Hey Sitecore Enthusiasts!

Sign up to our bi-weekly newsletter for a bite-sized curation of valuable insight from the Sitecore community.

What’s in it for you?

  • Stay up-to-date with the latest Sitecore news
  • New to Sitecore? Learn tips and tricks to help you navigate this powerful tool
  • Sitecore pro? Expand your skill set and discover troubleshooting tips
  • Browse open careers and opportunities
  • Get a chance to be featured in upcoming editions
  • Learn our secret handshake
  • And more!
Sitecore Snack a newsletter by Fishtank Consulting
 

Meet Dan Cruickshank

President | Sitecore MVP x 11

Dan is the founder of Fishtank. He's a multi-time Sitecore MVP and Coveo MVP award winner. Outside of technology, he is widely considered to be a top 3 father (routinely receiving "Father of the Year" accolades from his family) and past his prime on the basketball court.

Connect with Dan