Automated Testing in Sitecore Without an HttpContext
This year was the first year that Sitecore’s Dreamcore conference was held in Australia. And I for one jumped at the opportunity to speak at it. One topic that quite interests me is unit testing, and if you’ve read my posts over the years you’d see I have come up with a variety of techniques for getting unit testing (or rather, integration testing) working for Sitecore projects. So what better topic to talk about at Dreamcore than automated testing techniques? My session at Dreamcore covered a wide range of techniques and so I didn’t have time to go in depth with any single technique such as this one. In fact, I only had 40 minutes to speak and my rehearsal the night before I came in at 1 hour. So I’ll take this opportunity to explain the technique in depth. Many years ago I tried to run my automated tests for my Sitecore project’s inside the NUnit GUI test runner…and failed. The issue with the standalone test runner is that there is no HttpContext, and as I’m sure you’re well aware, Sitecore requires one of these to work. When I ran into this hurdle all those years ago, I instead developed different techniques to get around this limitation. Anyway, back to this year. A few months before Dreamcore I was thinking I had to have another look into getting my automated tests for Sitecore running without the HttpContext. And then Mike Edwards beat me to the punchline and posted a blog entry on how he got unit testing for Sitecore working in the NUnit GUI. Mike’s post proved to me that it was possible, so I followed his post and got some tests running inside the NUnit GUI test runner, calling into the Sitecore 6.5 API. With Mike’s general direction in place I set about fully integrating this approach to testing into a project I was working on at the time, which would provide a bit more context for me when I was writing my Dreamcore presentation. The main change I made to Mike’s approach was to copy the configuration over from an existing Sitecore project. This way if any of the Sitecore configuration changed my test project would also see that change and the code under test would be run in an environment as close as possible to production. First thing’s first, create a new class library project to write the tests in and add references to the nunit.framework
assembly. As we’ll be calling into the Sitecore API (the whole point of this exercise) we’ll also need to add a reference to the Sitecore.Kernel
assembly from the Sitecore instance the tests will be written against. To make sure we’ve got the configuration right to use the Sitecore API we’ll also create a simple test which uses the Sitecore API. The simplest thing I can think of would be grabbing a field from the home item. The following code shows how to implement this test.
[TestFixture]
public class ApiTest
{
[Test]
public void AccessFieldOnHome()
{
var db = Sitecore.Configuration.Factory.GetDatabase("web");
var home = db.GetItem("/sitecore/content/home");
var fieldValue = home["title"];
Assert.AreEqual("Sitecore", fieldValue);
}
}
The above code will also require a reference to System.Configuration
due to the call to the Sitecore.Configuration
namespace. Note above how we need to retrieve the database and cannot use the Sitecore context to get the context database because the context hasn’t been populated. The Sitecore context is populated through the httpRequestBegin
pipeline which isn’t run when we’re calling the API outside an HttpContext. If we were to run the above test we would end up with that familiar “Failure: System.InvalidOperationException : Could not read Sitecore configuration.” error. Have you ever noticed how so many people lack the ability to read and interpret error messages? The above error message gives a clear description of the issue…there is no Sitecore configuration. So onto the biggest hurdle, copying the Sitecore configuration over from the Sitecore project which these tests will exercise. I want to automate this process and make it part of the project build process so I get any configuration updates in the test project. This will require some MSBuild additions. To edit the test project’s MSBuild unload the project in Visual Studio then right click the project and select “Edit TestProject.csproj”. Alternatively you could directly edit the project file (csproj) using a separate text editor and Visual Studio will prompt you to reload the project file when you go back into Visual Studio. However the benefit of editing the project file inside Visual Studio is that Visual Studio will provide IntelliSense as you edit the file. To make maintenance easier we’ll store the path to the Sitecore instance we’ll be using the configuration from in a variable. While editing the project file add the following script to the first PropertyGroup
element which doesn’t have any additional attributes.
<SitecorePath>C:\inetpub\WeBlog-65\Website</SitecorePath>
Make sure you update the path in the variable to reference the local path on your machine. The above script defines a variable called SitecorePath
and sets it to the path provided. This variable can then be used throughout the rest of the script. Now Find the AfterBuild
target which will be commented out. This target will be called by Visual Studio if the build was successful. This is where we’ll put our additional MSBuild configuration. Uncomment the AfterBuild target and add the following script to it.
<Copy SourceFiles="$(SitecorePath)\web.config"
DestinationFiles="$(OutputPath)\$(AssemblyName).dll.config" />
The above MSBuild script will copy the web.config
of the referenced Sitecore instance to the configuration file of the test assembly. Remember for Windows assemblies the configuration file is not named web.config
but is instead the same name as the assembly with an additional .config
at the end. So to provide configuration for my program MyApp.exe
the configuration file must be named MyApp.exe.config
. The same works for the test assemblies. Sitecore configuration is actually spread over a number of additional files, so the next piece of script will grab those files and copy them over to the appropriate location as well.
<CreateItem Include="$(SitecorePath)\App_Config\**\*.*">
<Output ItemName="configFiles" TaskParameter="Include" />
</CreateItem>
<!-- Copy relative external source files -->
<Copy SourceFiles="@(configFiles)"
DestinationFolder="$(OutputPath)\App_Config\%(RecursiveDir)" />
Note how in the DestinationFolder
attribute I’m using the RecursiveDir
well-known metadata about the input files. This will create the same directory structure as the source files are in instead of dumping them out to a flat directory. To make sure we have all the assemblies we might require we will also need to copy all the assemblies in the Sitecore bin folder to the same location as the test assembly.
<CreateItem Include="$(SitecorePath)\bin\*.dll">
<Output ItemName="binaryFiles" TaskParameter="Include" />
</CreateItem>
<Copy SourceFiles="@(binaryFiles)" DestinationFolder="$(OutputPath)" />
And now, the test will work! Though we’re missing one important piece of configuration…configuration include files. Configuration includes files are located in the \App_Config\Include
folder of a Sitecore instance and allow patching the Sitecore configuration. This makes it much easier to keep your project configuration separate from the Sitecore configuration. It also makes upgrading Sitecore much easier as you can simply take the entire updated web.config
file (Sitecore section) without worry that you’re reverting some update made for your project. There are 2 options for handling the configuration include files. Firstly, don’t use them. For a test project you could just make sure all the configuration required is contained in the single web.config file. But that’s going against what I’ve been talking about in terms of separating project configuration from Sitecore configuration and using a real project’s configuration to ensure the tests are run in a realistically configured environment. The other option is to put the include files where they’re required to allow the configuration patching utility to find them. The script above is already taking care of copying the include files to the output folder, so what else needs to be done? The configuration patch utility used by Sitecore internally contains a call to the Server.MapPath()
method. When there is no HttpContext this method will simply pass back the path as it was passed in. Sitecore is trying to locate the folder relative to the web application root, so what’s passed to the method is /App_Config/Include
. When trying to locate this path on a disk the leading slash will cause the OS to look from the root of the disk, so the path being located is actually c:\App_Config\include
. To make the configuration include files work we’ll need to copy the files to a folder off the root of the disk named App_Config
.
<Copy SourceFiles="@(configFiles)"
DestinationFolder="c:\App_Config\%(RecursiveDir)" />
Now the configuration patch utility will work. Some points of caution when using this technique. Some components won’t be in the correct state when you try and use them because none of the request process has been run. No ASP.NET events and no Sitecore pipelines have been run. This is why the Sitecore context is not populated. But I’ve also found some other classes such as Sitecore.Globals
aren’t initialised. If your test uses any of the global objects (Links DB, Tasks DB) you’ll need to manually initialise them with a call to Sitecore.Globals.Load();
before using them. I’ve recently spoken to one of the Sitecore Users Virtual Group users who has been using this technique who had issues calling Delete()
on an item, because of the uninitialised Tasks database. Another method I’ve found which simply won’t work is the WebControl.RenderAsText()
method. This method relies on there being an HttpContext and simply won’t work without one. The above points of caution are just a few I’ve found whilst using this technique. That’s not to say there aren’t a heap of other classes which need initialising before use that I haven’t run into yet. This technique is also quite heavy. The Sitecore application is actually running. A real Sitecore database it accessed. Many of the objects defined in the configuration are created and strung together. Although anecdotally comparing the speed of the test runs using this technique to my normal technique of using an embedded test runner, this technique does seem faster. If you’d like a working example of this technique you can refer to my materials from Dreamcore Australia 2011. I was lucky enough to recently deliver the same presentation to the Sitecore Users Virtual Group, so if you’d like to see me put those pieces together then you can refer to the recording of my Testing Strategies for Sitecore presentation on Hedgehog’s YouTube channel.
[…] / Jenkins There is also this interesting article about Sitecore Unit testing without a HTTP […]