Mocking Sitecore using MS Fakes

mock ms fakes Sitecore unit test

Mocking Sitecore has been a tough nut to crack since Sitecore lacks the necessary interfaces that common mocking frameworks need, which really screws up automated testing for a lot of us. For that we turn to isolation frameworks like TypeMock and Justmock which unfortunately come with a huge price tag. Thankfully Microsoft Fakes is here to provide an alternative that may not be everyone’s cup of tea but does the job alright. If you’re using Visual Studio Premium, Ultimate, or Enterprise then you’re in for a treat because MS Fakes is already included in these versions for no extra cost.

Below are functions that can ramp you up with faking Sitecore item, template, and field collections, but for better understanding on how MS Fakes works I’d recommend reading the official Microsoft blog first: https://msdn.microsoft.com/en-us/library/hh549175.aspx

Faking the Item

private readonly Language ContextLanguage = Language.Parse("en");

ShimItem CreateFakeItem(ShimItem parentItem, string name, Action<ShimItem, ShimTemplateItem, List<ShimField>> onItemCreating)
{
    var id = ID.NewID;

    var item = new ShimItem()
    {
	 // Assigning ID.NewID directly will return a new ID every time item.ID is called
        IDGet = () => id,
        KeyGet = () => name.ToLower(),
        NameGet = () => name,
        HasChildrenGet = () => false,
        ParentGet = () => parentItem,
        PathsGet = () =>
        {
            var path = (parentItem != null ? parentItem.Instance.Paths.Path : "") + "/" + name;

            return new ShimItemPath()
            {
                PathGet = () => path,
                FullPathGet = () => path,
            };
        },
        LanguageGet = () => ContextLanguage,
        VersionsGet = () => new ShimItemVersions() { CountGet = () => { return 1; } }
    };

    // Bind item to parent item
    if (parentItem != null)
    {
        var children = parentItem.Instance.HasChildren ? parentItem.Instance.Children.ToList() : new List<Item>();
        children.Add(item);

        parentItem.HasChildrenGet = () => true;
        parentItem.ChildrenGet = () => new ChildList(parentItem.Instance, children);
        parentItem.GetChildren = () => parentItem.Instance.Children;
    }

    // Start faking template and field collection
    var templateItem = new ShimTemplateItem();
    var fields = new List<ShimField>();

    // Call action to allow extension of item, template, and field collection faking    
    onItemCreating(item, templateItem, fields);

    item.TemplateGet = () => templateItem;
    item.FieldsGet = () => CreateFakeFieldCollection(item, fields);

    return item;
}

Faking the Field Collection

// Create a dictionary to hold the field collection per item ID
private Dictionary<ID, List<ShimField>> itemFields = new Dictionary<ID, List<ShimField>>();

ShimFieldCollection CreateFakeFieldCollection(ShimItem item, List<ShimField> fields)
{
    foreach (var field in fields)
        field.ItemGet = () => item;

    var fieldCollection = new ShimFieldCollection()
    {
        ItemGetString = (fieldName) =>
        {
            return fields.SingleOrDefault(n => n.Instance.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase));
        }
    };

    if (!itemFields.ContainsKey(item.Instance.ID))
        itemFields.Add(item.Instance.ID, fields);
    else
        itemFields[item.Instance.ID] = fields;

    fieldCollection.Bind(itemFields[item.Instance.ID]);

    return fieldCollection;
}

Faking the Sitecore Context

Normally a Sitecore item is retrieved using Sitecore.Context.Database.GetItem function, therefore we’re gonna need to fake the Context.

void FakeSitecoreContext()
{
    ShimContext.LanguageGet = () => ContextLanguage;
    ShimContext.SiteGet = () => new ShimSiteContext()
    {
        ContentLanguageGet = () => ContextLanguage
    };

    Func<Func<Item, bool>, Item> getItem = (predicate) =>
    {
        Item result;

        return TryGetItem(this.Sitecore.Instance.Children, predicate, out result) ? result : null;
    };

    ShimContext.DatabaseGet = () => new ShimDatabase()
    {
        GetItemString = (path) => getItem(n => n.Paths.Path.Equals(path, StringComparison.OrdinalIgnoreCase)),
        GetItemStringLanguage = (path, lang) => getItem(n => n.Paths.Path.Equals(path) && (n.Language.Equals(lang) || n.Languages != null && n.Languages.Any(l => l.Name.Equals(lang.Name)))),
        GetItemID = (id) => getItem(n => n.ID.Equals(id)),
        GetItemIDLanguage = (id, lang) => getItem(n => n.ID.Equals(id) && (n.Language.Equals(lang) || n.Languages != null && n.Languages.Any(l => l.Name.Equals(lang.Name)))),
    };
}

bool TryGetItem(ChildList children, Func<Item, bool> predicate, out Item result)
{
    result = null;

    if (children == null || !children.Any()) return false;

    result = children.FirstOrDefault(predicate);

    if (result != null) return true;

    var query = children.Where(n => n.HasChildren);

    if (!query.Any()) return false;

    foreach (var child in query.ToArray())
    {
        if (TryGetItem(child.Children, predicate, out result))
            return true;
    }

    return false;
}

Faking the Base Item

We are going to have to fake the Base Item class so that we can use the item’s indexed property and get the value of a field like in item[“Fieldname”].

void FakeBaseItem()
{
    ShimBaseItem.AllInstances.ItemGetString = (baseItem, fieldName) =>
    {
        Item result;

        TryGetItem(Sitecore.Instance.Children, (n) => object.Equals(baseItem, n), out result);

        if (result != null)
        {
            var fields = itemFields[result.ID];

            var field = fields.FirstOrDefault(n => n.Instance.Name.Equals(fieldName, StringComparison.OrdinalIgnoreCase));

            if (field != null) return field.Instance.Value;
        }

        return string.Empty;
    };
}

Faking the Sitecore tree

Now we can start faking the Sitecore tree and organize it accordingly.

public ShimItem Sitecore, Content, Site, Home;
public void Initialize(Action onInitializing = null)
{ 
    Sitecore = CreateFakeItem(null, "sitecore", (sitecore) => {
        Content = CreateFakeItem(sitecore, "content", (content) =>
        {
            Site = CreateFakeItem(content, "site", (site) =>
            {
                Home = CreateFakeItem(site, "home", (home) =>
                {
			// Add more items if you must to mimic your Sitecore tree
                });
            });
        });
    });

    if (onInitializing != null)
        onInitializing(this);

    FakeBaseItem();
    FakeSitecoreContext();
}

Of course a CreateFakeItem overload is necessary for this to work:

public ShimItem CreateFakeItem(ShimItem parentItem, string name, Action onItemCreating)
{
    return CreateFakeItem(parentItem, name, (i, t, f) =>
    {
        if (onItemCreating != null)
            onItemCreating(i);
    });
}

The Sample Test

Shimming requires that you create your shim objects inside a using statement that has a disposable ShimsContext. Instead of doing this I personally like to create a ShimsContext object during the test initialization and destroy it during the test cleanup. It works the same way as with the using statement but with a lot less coding.

[TestClass]
public class SampletTests
{
    IDisposable _context;
    readonly SitecoreFaker _scFaker = new SitecoreFaker();

    [TestInitialize]
    public void Initialize()
    {	 
        _context = ShimsContext.Create();
        _scFaker.Initialize();
    }

    [TestCleanup]
    public void Cleanup()
    {
        _context.Dispose();
    }

    [TestMethod]
    public void Sample_Test()
    {
        // Arrange:
        var expectedItem = _scFaker.CreateFakeItem(_scFaker.Home, "sample item");

        // Act:
        var actualItem = Context.Database.GetItem(expectedItem.Instance.Paths.FullPath);

        // Assert:
        Assert.AreEqual(expectedItem, actualItem);
    }

}

I find it really handy to use this functionalities for all my Sitecore related unit tests. The only thing that I am not really happy about is that I always have to use Shim objects which Microsoft has documented to degrade the testing performance. Running the tests using vstest.console.exe makes up for it though.

4 Thoughts to Mocking Sitecore using MS Fakes

Comments are closed.

  • Nitin Shukla
    Nitin Shukla

    Hi Jeff,

    Your Blog Post has been a major Life saver as i have shaped my entire Unit Testing solution around this awesome piece of code.But i have ran into a bit of a roadblock when i try to access the fake Items from my unit tests. I have put up a detailed question regarding my blockage here:

    https://stackoverflow.com/questions/46618714/not-able-to-access-sitecore-mock-items-ms-fakes-through-sitecore-context-in-un

    Please answer if time allows for it.

    07.10.2017 11:41
    • Jeff Sulit
      Jeff Sulit

      Hi Nitin,

      I’ve replied to your question on stackoverflow.

      Let’s continue solving your problem there.

      Cheers,
      Jeff

      08.10.2017 21:23
  • Nitin Shukla
    Nitin Shukla

    HI,

    Very Informative and interesting Post. Just what i need to Unit Test Sitecore but one question though…You have always defined List() as a Method and nowhere as List…this is throwing an error when i am trying to implement your solution in my Project. Can you explain this to me….? Pardon my lack of Knowledge as i am new to both Sitecore and Unit Testing. A quick prompt reply would be a lifesaver.

    Thanks,
    Nitin

    07.09.2017 21:39
    • Jeff Sulit
      Jeff Sulit

      Hi Nitin,

      Apologies for this inconvenience and thanks for pointing out the errors in my code. I have since corrected it, so I hope this solves your build issues. Let me know if there’s anything else I can help with. Cheers!

      08.09.2017 00:31