25

Is it possible to create a mock Sitecore (using Sitecore 8.2) item using NSubstitute? I would like to write a unit test to cover the following pipeline processor code, which adds a child folder item called "Items" if it does not exist.

public void Process(GetRenderingDatasourceArgs args)
{
  var renderingItem = args.RenderingItem;

  if (!renderingItem.GetChildren().Any(item => item.Name == "Items" && item.TemplateID == TemplateIDs.Folder))
    renderingItem.Add("Items", new TemplateID(TemplateIDs.Folder));
}

I would like to write unit tests that cover the following scenarios:

  • When the passed in item does not have a child "Items" folder then one is created. The substitute item should return a child item of the correct type, and should be able to verify that the Add method was not called.
  • When it does have a child "Items" folder another one is not created. The substitute item should not return any children, and should confirm that the Add method was called with the correct parameters.

2 Answers 2

23

This is possible with Sitecore 8.2, since the GetChildren and Add methods became virtual with this version. (You could create an NSubstitute Sitecore 8.1 item, but NSubstiute would not be able to interact with its methods, so there would not be any point in doing so.)

To create a Subsitute item, you need to:

  1. Create an item ID:

    var itemId = ID.NewID;
    
  2. Create an item definition:

    var definition = new ItemDefinition(itemId, string.Empty, ID.Null, ID.Null);
    
  3. Create an item data object:

    var data = new ItemData(
      definition, 
      Language.Current, 
      Sitecore.Data.Version.First, 
      new FieldList());
    
  4. Create a substitute database, which is easy in Sitecore 8.2 because the class is now abstract:

    Database db = Substitute.For<Database>();
    
  5. Create the substitute item, passing in the itemID, ItemData object, and substitute database as parameters:

    Item item = Substitute.For<Item>(itemId, data, db);
    
  6. If the code under test interacts with the Paths property, you will need to set this:

    item.Paths.Returns(Substitute.For<ItemPath>(item));

Now that you have a Substitute item, you can use the Returns and Received methods as normal with NSubstitute:

public class ProcessorTests
{
    [Fact]
    public void Processor_ItemsFolderMissing_CreatesIt()
    {
        var sut = new DataSourceFolderCreator();
        Item renderingItem = MakeSubstituteItem();
        renderingItem.SetChildren(new ItemList());
        renderingItem.Database.Should().NotBeNull();

        var args = new GetRenderingDatasourceArgs(renderingItem, renderingItem.Database);

        sut.Process(args);

        renderingItem.Received()
          .Add("Items", new TemplateID(TemplateIDs.Folder));
    }

    [Fact]
    public void Processor_ItemsFolderPresent_DoesNotCreateIt()
    {
        var sut = new DataSourceFolderCreator();
        var folderItem = MakeSubstituteItem();
        var renderingItem = MakeSubstituteItem();
        folderItem.Name.Returns("Items");
        folderItem.TemplateID.Returns(TemplateIDs.Folder);
        renderingItem.SetChildren(new ItemList { folderItem });
        var args = new GetRenderingDatasourceArgs(renderingItem, renderingItem.Database);

        sut.Process(args);

        renderingItem.DidNotReceiveWithAnyArgs().Add("", new TemplateID());
    }

    private Item MakeSubstituteItem()
    {
        var itemId = ID.NewID;
        var definition = new ItemDefinition(
          itemId, string.Empty, ID.Null, ID.Null);
        var data = new ItemData(
          definition, Language.Current,
          Sitecore.Data.Version.First, new FieldList());
        var db = Substitute.For<Database>();

        var item =  Substitute.For<Item>(itemId, data, db);
        item.Paths.Returns(Substitute.For<ItemPath>(item));

        return item;
    }
}

public static class ItemExtensions
{
    public static void SetChildren(this Item item, ItemList items = null)
    {
        if (items == null)
        {
            items = new ItemList();
        }
        item.GetChildren().Returns(new ChildList(item, items));
    }
}
2
10

Mocking Pipeline Code in Sitecore before 8.2

If you are working in Sitecore before 8.2 your best bet would be to look into using Sitecore FakeDB; it offers an in-memory representation of Sitecore.

It allows tests for all parts of Sitecore including pipeline code.

An example from the documentation is:

[Fact]
public void HowToUnitTestAdvancedPipelineCallWithMockedProcessor()
{
  var args = new Sitecore.Pipelines.PipelineArgs();

  using (Sitecore.FakeDb.Db db = new Sitecore.FakeDb.Db())
  {
    // register a processor mock in the Pipeline Watcher
    var processor =
      Substitute.For<Sitecore.FakeDb.Pipelines.IPipelineProcessor>();

    processor
      .When(p => p.Process(args))
      .Do(ci => ci.Arg<Sitecore.Pipelines.PipelineArgs>()
                  .CustomData["Result"] = "Ok");

    db.PipelineWatcher.Register("mypipeline", processor);

    // call the pipeline
    Sitecore.Pipelines.CorePipeline.Run("mypipeline", args);

    // check the result is set
    Assert.Equal("Ok", args.CustomData["Result"]);
  }
}

It also allows mocking for:

  • Authentication
  • Authorization
  • Role
  • Roles-In-Roles
  • Membership

References

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.