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!