Mocking Sitecore using MS Fakes

testing-a-tomato

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> 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();
        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();

    // 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> itemFields = new Dictionary<ID, List>();

ShimFieldCollection CreateFakeFieldCollection(ShimItem item, List 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 ShimcContext 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.

To view the complete project please visit the Sitecore.Fakes Github repository:
https://github.com/kumagron/Sitecore.Fakes

Advertisements