Unit testing in Sitecore

Sitecore became test-friendly as soon as Dependency Injection was added in with 8.2 release:

public class Dummy
{
    private readonly BaseItemManager _baseItemManager;

    public Dummy(BaseItemManager itemManager)
    {
        _baseItemManager = itemManager;
    }

    public string Foo(ID id, ID fieldID)
    {
        // Legacy approach with static manager
        // var item = Sitecore.Data.Managers.ItemManager.GetItem(id);
        var item = _baseItemManager.GetItem(id); 
        return item[fieldID];
    }
}

However, straightforward unit test would have a long arrange for Sitecore entities:

public class DummyTests
{
    [Theory, AutoData]
    public void Foo_Gets_ItemField(ID itemId, ID fieldId, string fieldValue)
    {
        var itemManager = Substitute.For<BaseItemManager>();
        var database = Substitute.For<Database>();
        var item = Substitute.For<Item>(itemId, ItemData.Empty, database);
        item[fieldId].Returns(fieldValue);
        itemManager.GetItem(itemId).Returns(item);
        var sut = new Dummy(itemManager);

        var actual = sut.Foo(itemId, fieldId);        
        actual.Should().Be(fieldValue);
    }
}

8 lines of code (>550 chars) to verify a single scenario is too much code.

How to simplify unit testing?

A big pile of solution code is typically build around:

  • Locating data by identifier (GetItem API)
  • Processing hierarchies (Children, Parent, Axes)
  • Filtering based on template
  • Locating specific bits (accessing fields)

The dream test would contain only meaningful logic without arrange hassle:

[Theory, AutoNSubstitute]
public void Foo_Gets_ItemField(FakeItem fake, [Frozen] BaseItemManager itemManager, Dummy sut, ID fieldId, string fieldValue)
{
    Item item = fake.WithField(fieldId, fieldValue);        
    itemManager.GetItem(item.ID).Returns(item);

    var actual = sut.Foo(item.ID, fieldId);
    actual.Should().Be(fieldValue);
}

Better? Let’s take a closer look what has changed so that test is only 4 lines now.

Building items via SitecoreDI.NSubstitute.Helper

Sitecore.NSubstituteUtils builds anything related to item in builder pattern:

var bond = new FakeItem()
            .WithName("Bond, James Bond")
            .WithLanguage("EN")
            .WithField(FieldIDs.Code, "007")
            .WithTemplate(IDs.LicenseToKill)
            .WithChild(new FakeItem())
            .WithParent(_M)
            .WithItemAccess()
            .WithItemAxes()
            .ToSitecoreItem();

Code samples were crafted to make a learning curve as easy as it could be imagined.

To cut it short – all major item properties can be arranged by this engine.

Inject items into tests via AutoFixture

AutoFixture creates all the needed entities if taught how to:

 public class AutoNSubstituteDataAttribute : AutoDataAttribute
    {
        public AutoNSubstituteDataAttribute()
            : base(() => new Fixture().Customize(
                new CompositeCustomization(
            new DatabaseCustomization(),
            new ItemCustomization().....))
        {
        }
    }

    public class ItemCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            fixture.Register<ID, Database, FakeItem>((id, database) => new FakeItem(id, database));
            fixture.Register<FakeItem, Item>(fake => fake.ToSitecoreItem());
        }
    }

    public class DatabaseCustomization : ICustomization
    {
        public void Customize(IFixture fixture)
        {
            fixture.Register<string, Database>(FakeUtil.FakeDatabase);
        }
    }

Implicit dependency warning: Sitecore.Context

Test isolation is threatened by an implicit dependency on Sitecore.Context (which is static on the surface). There are multiple solutions on the deck.

A) Clean up Sitecore.Context inner storage in each test

Context properties are based on Sitecore.Context.Items (backed either by HttpContext.Items or Thread-static) dictionary that could be cleaned before/after each test execution so that context property change effect is gone once test finishes:

public class DummyTests: IDisposable
{
    public DummyTests()
    {
        Sitecore.Context.Items.Clear();
    }

    public void Foo() 
    {
        Sitecore.Context.Item = item;
        ....
    }
    
    void IDisposable.Dispose() => Sitecore.Context.Items.Clear();
}

The approach leads to burden/hidden dependency that is a code smell.

B) Facade Sitecore.Context behind ISitecoreContext

All the custom code could use ISitecoreContext interface instead of Sitecore.Context so that all the needed dependencies become transparent:

interface ISitecoreContext 
{
  Item Item { get;set; }
  Database Database { get;set; }
  ...
}

public class SitecoreContext: ISitecoreContext
{
  public Item Item 
    { 
      get => Sitecore.Context.Item;
      set => Sitecore.Context.Item = value;
    }

  public Database Database
    { 
      get => Sitecore.Context.Database;
      set => Sitecore.Context.Database = value;
    }
  ...
}

The implementation can be registered as transient in DI config & consumed via constructor injection:

public class Dummy
{
    private readonly ISitecoreContext _sc;

    public Dummy(ISitecoreContext sc)
    {
        _sc = sc;
    }

    public string Foo(ID fieldID)
    {
        var contextItem = _sc.Item;
        return contextItem[fieldID];
    }
}

Summary

The approach allows writing tests in easy manner making excuse card ‘Sitecore is not testable‘ to fade into the history.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: