codeflood logo

Sitecore 8.2: Mock your Items

Back in January I wrote an article about how to Roll your own Sitecore Item, which showed how to create an in-memory Sitecore item for use in testing. The technique was quite limited and still required some small config entry to allow everything to work (to create a Database instance). Well, Sitecore 8.2 has finally made a reality what so many of us have prayed for, for so long. We can finally mock Items and Databases with Sitecore 8.2. Though, it's probably not quite as simple as you might hope.

Keep in mind, items aren't DTOs to be newed up here and there. An Item is instantiated within the data layer, so it doesn't have a parameterless constructor, which is required by many mocking frameworks. To mock an Item, we still need the arguments for it's constructor. So here's the minimal code to mock an Item. Note how we can mock the DB now. BTW, I'm using Moq as my mocking framework.

var id = ID.NewID;
var templateId = ID.NewID;
var def = new ItemDefinition(id, "fake", templateId, ID.Null);
var data = new ItemData(def, Language.Parse("en"), 
  new Sitecore.Data.Version(1), new FieldList());

var db = Mock.Of<Database>();
var itemMock = new Mock<Item>(id, data, db);
var item = itemMock.Object;

That item there is not a real Item, it's a mocked item! And because we're mocking, and all the Item members are now virtual, we can use setups to configure member behavior. This allows us to fill in gaps from the previous technique like accessing fields by name.

itemMock.SetupGet(x => x["field"]).Returns("value");
var item = itemMock.Object;

var value = item["field"];
Assert.That(value, Is.EqualTo("value"));

Or how about accessing the content tree? We'll need a few mock items for this setup, so let's wrap up the above code in a utility method we can call to create a mocked item named CreateMockedItem(). Now we can setup the Children property to return a list of mocked items:

var item = itemMock.Object;

var children = new ChildList(item, new[]
{
    CreateMockedItem(),
    CreateMockedItem()
});

itemMock.SetupGet(x => x.Children).Returns(children);

var childItem = item.Children[1];
Assert.That(childItem, Is.Not.Null);

How about creating items? We can setup the Add() method to return a mocked item as well:

var addedItem = CreateMockedItem();
itemMock.Setup(x => x.Add(It.IsAny<string>(), It.IsAny<TemplateID>()))
  .Returns(addedItem);
var item = itemMock.Object;

var newItem = item.Add("my new item", new TemplateID(ID.NewID));
Assert.That(newItem, Is.Not.Null);

OK, lot's of good stuff there. But not all scenarios only use direct members of the item. Some scenarios we'll want to run will need to mock objects which the item exposes. Let's access the name of the item's template. The Template property of the Item gives us access to the template and the Name property hangs off that (Let's ignore the TemplateName property of Item for a second). We'll need to mock the template, and then mock Template access on the Item to return the mocked template:

var fakeTemplateItem = CreateMockedItem();
var templateMock = new Mock<TemplateItem>(fakeTemplateItem);
templateMock.SetupGet(x => x.Name).Returns("fake template name");
var template = templateMock.Object;

itemMock.SetupGet(x => x.Template).Returns(template);

var item = itemMock.Object;
var templateName = item.Template.Name;

Assert.That(templateName, Is.EqualTo("fake template name"));

So yeah, we've got the ability to mock an Item. But given how much code we need to write to fulfill many scenarios, perhaps mocking isn't always what we're after. FakeDB still makes plenty of scenarios much easier to implement with less code than when we mock everything ourselves. So add Item mocking to your toolbox. Just remember to use the right tool for the job.

Comments

Thanks! This was a great intro into 8.2 and mocking, need to try this out. With the DI and all new features, this is hopefully getting easier.

Vince

Hi
We have used the technique to mock a Sitecore Item. It required us to add the following dependencies to our __test__ projects:
* Microsoft.Extensions.DependencyInjection * Microsoft.Extensions.DependencyInjection.Abstractions * Sitecore.Logging * Lucene.Net
As they are not direct dependencies, they show up as unused references in Visual Studio.
When these tests are run in our CI server, it can't find these dependencies and so the tests fail, stating it can't find those dependencies.
Any ideas?
Thanks in advance!

Alistair Deneys

Hi Vince, You'll need to make those types available on your CI server, where the test assemblies are run. I would suggest you use the same technique you're currently using to bring in the other Sitecore files such as Kernel; if you're using the files from the Sitecore zip, add these additional files to a lib folder in source control. If you don't have a reference to these assemblies in your test assembly, then you'll also need to ensure they get copied to the same location as your test assembly. I typically do that kind of thing with a post build step in MSBuild.

Leave a comment

All fields are required.