Roll your own Sitecore Item
One of the most difficult aspects of unit testing against Sitecore is mocking Sitecore items. The Sitecore
Item class is quite a complex class and it’s interface is not structured the way most mocking frameworks require. Mocking frameworks allow you to create dummy objects for use inside tests. But that’s not the only way to create dummy objects. One can always instantiate a class if the classes interface allows it. You might be surprised to learn that you can in fact create an instance of a Sitecore Item in your own code. Although the resulting object will only work for a limited scope of scenarios. Heed these warnings when using this technique:
- The axes won’t work. The content structure is not provided by the item, it’s provided by the
ItemManager. So you’ll not be able to traverse the content tree, look at parents or children, etc.
- Fields can only be accessed through IDs. This is a bit of a pain, and limits the use of this technique significantly, but the resolution of a field name to an ID is done through the
TemplateManager, which of course we’ll not be creating.
- Only attributes of the item will work. Many attributes of an item are provided by other classes. For example, accessing a template to get the template name, this is again done by the
TemplateManager, so we’ll not be able to access those more complex attributes.
Start by creating a new Class Library project in Visual Studio and add a reference to your favorite unit testing framework. As we’ll be creating an instance of
Sitecore.Data.Items.Item we’ll also need a reference to the
Sitecore.Kernel assembly. I’m using the kernel from Sitecore 8.1 Update 1. The
Item class only has a single constructor which needs a few things passed in. Let’s get to creating the dependent objects.
var id = ID.NewID; var templateId = ID.NewID; var titleFieldId = ID.NewID; var categoryFieldId = ID.NewID; var def = new ItemDefinition(id, "fake", templateId, ID.Null); var fields = new FieldList(); fields.Add(titleFieldId, "title"); fields.Add(categoryFieldId, "cat1"); var data = new ItemData(def, Language.Parse("en"), new Sitecore.Data.Version(1), fields);
This covers off the objects we need for the constructor with the exception of one, we also need a
Sitecore.Data.Database. Back in the Sitecore 6.x days,
Database had a public constructor which we could use. But this was made
internal in Sitecore 7.0. I can hear your groans, but trust me, this is for the best. We shouldn’t be newing up databases in our code, we should be getting them from the factory. A few years back I had to fix an excessive memory usage issue (there’s no such thing as “memory leaks” in .net) and the culprit was someone was newing up the master database in some custom code. So how do we go about pulling a DB out of the factory when we’re not inside a Sitecore application? Well, we need a little bit of configuration. Add an
App.config file to your project and add the following configuration to it.
<configuration> <configSections> <section name="sitecore" type="Sitecore.Configuration.ConfigReader, Sitecore.Kernel" /> </configSections> <sitecore> <databases> <database id="dummy" singleInstance="true" type="Sitecore.Data.Database, Sitecore.Kernel"> <param desc="name">$(id)</param> </database> </databases> </sitecore> </configuration>
This is the minimal config we need to define a “dummy” database. The reason this works is because we’re just using this database as a reference. We’ll not be making any calls into the database methods as they’ll likely fail. Now we have a database, we can complete our Item instantiation code.
var db = Factory.GetDatabase("dummy"); var item = new Item(id, data, db); Assert.That(item.ID, Is.EqualTo(id)); Assert.That(item.Name, Is.EqualTo("fake")); Assert.That(item.TemplateID, Is.EqualTo(templateId)); Assert.That(item[titleFieldId], Is.EqualTo("title"));
With all the code in place we can now compile and execute the test with the test runner. All assertions should pass.
Although this technique is very limited in what it can do, hopefully it can help save you from letting one more unit test slip into an integration test, when all you need is some simple information from an item for your code under test. If you need more functionality such as access to the content heirarchy (axes) or access to the template, then you’ll want to be using something like FakeDB which is a unit test framework provides full item functionality in memory.
Great post, as always. One note on the internal constructor for Sitecore.Data.Database. You can get around this by using a proxy tool like NSubstitute, because Siteocore.Kernell.dll exposes its internals to proxies made by Castle.Core (HT Mark Cassidy). See http://stackoverflow.com/a/38934233/402949 and http://stackoverflow.com/a/17179923/402949
As you point out, this is NOT a good idea, but it is technical feasible. Since almost all* of SItecore.Data.Database’s members are not virtual, this is basically the same as calling new Database(“name”).