Advanced Unit Testing of Sitecore 9 with FakeDb and NSubstitute

Aaron H
Principal Architect
  • Twitter
  • LinkedIn


Introduction

Unit testing is an essential part of maintaining quality in software development. In this blog post, we will explore how to perform advanced unit testing for Sitecore 9 using two powerful tools: FakeDb and NSubstitute. This post is designed for readers with a basic understanding of unit testing, Sitecore 9, FakeDb, and NSubstitute. While the FakeDb documentation provides some examples, our goal here is to expand on those examples and offer quick references for common Sitecore testing scenarios.

Setup

Before diving into the examples, let's address some setup requirements. For Sitecore versions 8.2 and above, your unit testing project must correctly set the

databaseType

variable to

Sitecore.Data.DefaultDatabase

in the app.config file. Failing to do so will result in a

Sitecore.Exceptions.ConfigurationException

when Sitecore attempts to initialize its

Database

class.

Additionally, you'll need the following NuGet packages:

  • Microsoft.Extensions.DependencyInjection
  • Sitecore.DependencyInjection
  • Sitecore.Mvc

These packages, in addition to FakeDb and NSubstitute, will support the examples provided.

Mocking Built-In Sitecore Nodes

A common challenge when unit testing with Sitecore is mocking the built-in Sitecore nodes. These nodes, such as Layout Root, Media Library, and System Root, are often referenced in custom code. Fortunately, FakeDb allows us to easily mock these nodes and set up a mock Sitecore environment.

Example 1: Mocking Layout Root

using (Db db = new Db()
{
    new DbItem("Layout", ItemIDs.LayoutRoot, TemplateIDs.MainSection)
    {
        ParentID = ItemIDs.RootID,
        FullPath = "/sitecore/layout",
        Children =
        {
            new DbItem("Devices", ItemIDs.DevicesRoot, TemplateIDs.Node)
            {
                new DbItem("Default", deviceId, TemplateIDs.Device)
                {
                    { DeviceFieldIDs.Default, "1" }
                }
            },
            new DbItem("Layouts", ItemIDs.Layouts, TemplateIDs.Node) { },
            new DbItem("Placeholder Settings", ItemIDs.PlaceholderSettingsRoot, TemplateIDs.Folder) { }
        }
    }
}) { }

This mock simulates the structure of Sitecore's layout system, including devices, layouts, and placeholders.

Example 2: Mocking Media Library Root

using (Db db = new Db()
{
    new DbItem("Media Library", ItemIDs.MediaLibraryRoot, TemplateIDs.MainSection) { }
}) { }

Similarly, this example mocks the Media Library root node, simulating the structure of the media library.

Mocking Common Field Types

Another common scenario involves testing code that expects specific field types, such as links, images, or checkboxes. FakeDb can help us mock these field types effectively.

Example 3: Mocking a Link Field

using (Db db = new Db()
{
    new DbItem("TestLinkItem", testLinkItemId),
    new DbItem("TestItem", testItemId)
    {
        new DbLinkField(linkFieldId)
        {
            TargetID = testLinkItemId,
            LinkType = "internal"
        }
    }
}) 
{
    BaseLinkManager linkManager = Substitute.For<BaseLinkManager>();
    linkManager.GetItemUrl(Arg.Is(i => i.ID == testLinkItemId)).Returns(itemLinkUrl);
    BaseFieldTypeManager fieldTypeManager = Substitute.For<BaseFieldTypeManager>();
    fieldTypeManager.GetField(Arg.Is(f => f.ID == linkFieldId), Arg.Any<Field>())
        .Returns(args => new LinkField(args[0] as Field));
    fieldTypeManager.GetFieldType(Arg.Is(v => v.ToLower() == "link"))
        .Returns(new FieldType("LinkField", typeof(LinkField), false, false));
}

This example mocks a link field, returning a predefined URL when the

GetItemUrl

method is called.

Example 4: Mocking an Image Field

using (Db db = new Db()
{
    new DbItem("MediaSource", mediaItemId),
    new DbItem("TestItem", testItemId)
    {
        new DbField(mediaFieldId) { Value = "media_url" }
    }
})
{
    MediaProvider mediaProvider = Substitute.For<IMediaProvider>();
    mediaProvider.GetMediaUrl(Arg.Is(i => i.ID == mediaItemId), Arg.Any<MediaUrlOptions>())
        .Returns(imageUrl);
}

This example mocks an image field, returning a mock media URL when the

GetMediaUrl

method is invoked.

Mocking Page Visualization

Sitecore’s rendering engine depends on the

Visualization.GetRenderings()

method to determine the renderings associated with a page. To mock this, we need to set up a mock database with the appropriate layout and rendering items.

Example 5: Mocking Layout and Renderings

using (Db db = new Db()
{
    new DbItem("Layout", ItemIDs.LayoutRoot, TemplateIDs.MainSection)
    {
        ParentID = ItemIDs.RootID,
        FullPath = "/sitecore/layout",
        Children =
        {
            new DbItem("Devices", ItemIDs.DevicesRoot, TemplateIDs.Node)
            {
                new DbItem("Default", deviceId, TemplateIDs.Device)
                {
                    { DeviceFieldIDs.Default, "1" }
                }
            }
        }
    }
}) 
{
    var templateLayout = "<layout>...</layout>";
    var itemDelta = "<rendering>...</rendering>";
    var layout = XmlDeltas.ApplyDelta(templateLayout, itemDelta);
    
    db.Add(new DbItem("test-page", pageItemId)
    {
        { FieldIDs.LayoutField, layout }
    });
    
    var renderings = db.GetItem("/sitecore/content/test-page").Visualization.GetRenderings(device, true);
}

This example sets up a test page with renderings using FakeDb and mocks the call to retrieve the renderings for the page.

Mocking Managers and Dependencies

To fully simulate Sitecore’s behavior during unit tests, it’s important to mock Sitecore’s Manager classes and dependencies.

Example 6: Mocking a Manager

var serviceCollection = new ServiceCollection();
new DefaultSitecoreServicesConfigurator().Configure(serviceCollection);

BaseMediaManager mediaManager = Substitute.For<BaseMediaManager>();
mediaManager.GetMediaUrl(Arg.Is(i => i.ID == mediaItemId), Arg.Any<MediaUrlOptions>())
    .Returns(imageUrl);

serviceCollection.AddSingleton(mediaManager);

var scopeFactory = serviceCollection.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
    var provider = scope.ServiceProvider;
    ServiceLocator.SetServiceProvider(provider);
    // Test code here (ACT & ASSERT)
}

This approach sets up an empty

ServiceCollection

, registers the

BaseMediaManager

, and ensures that the mock service is available for testing.

Conclusion

Unit testing in Sitecore can be complex due to its dependency on a large number of built-in components. However, with tools like FakeDb and NSubstitute, we can effectively mock Sitecore’s behavior and focus on testing our application logic. By mocking Sitecore nodes, field types, page renderings, and manager classes, we can create comprehensive unit tests that ensure the robustness of our code without needing a live Sitecore instance.

This reference should provide a solid foundation for anyone looking to perform advanced unit testing with Sitecore 9, FakeDb, and NSubstitute. Happy testing!