Sitecore wrapper classes - mock your Sitecore dependencies for TDD

I've recently pushed to GitHub some wrappers and interfaces for many of the common Sitecore API classes, which I put to good use on a recent project - this will hopefully save other people doing the same work! They can be found at https://github.com/RedMoonLimited/SitecoreWrappers - you'll also need to reference the Sitecore.Kernel and Sitecore.ContentSearch dlls; note that this code was written against Sitecore version 7.1 rev 140130. I've also included a few fairly hastily written "builder" classes (which may come in useful when creating common 'aggregates' of mock Sitecore objects for testing purposes) plus some example code and its unit test to get you started.

There are a few different ways you can wrap external dependencies for isolation/testing purposes - I've grown to like using wrappers which follow the contract of the wrapped class quite closely as, especially in the case of Sitecore, you can benefit from your knowledge of the "real" underlying code by pretty much using the wrapper classes in the same way. I guess this approach only works if you are unlikely to swap out the implementation of the abstractions (if you had to do this it's unlikely that the new implementation would match your abstraction as closely.) Additionally, this approach means you lose the ability to make any "improvements" to the original (wrapped) code. An alternative approach would be to wrap commonly used code as groups of services, e.g. SitecoreItemService, SitecoreDatabaseService etc.

So, as suggested above, I have tried to copy the signature of the wrapped Sitecore API methods as closely as possible. At the suggestion of one of my client's employees, I also introduced the concept of returning the same object from a property when practical; for instance, when asking an IItem for its Fields property, I reuse the FieldCollectionWrapper object I created previously, rather than instantiating a new one from the underlying Item's Fields property.

There were a few gotchas which became evident when replacing some existing Sitecore API code with the wrapper equivalents. An important one appeared when doing some null-checking - if a null Item is wrapped by an ItemWrapper class, this doesn't mean the ItemWrapper instance is null! So I started adding "IsNull" properties (which check whether the wrapped object is null) and replacing all comparisons with null to use this property instead. Casting is also something which doesn't play well with the wrapper mentality, so I have cheated and added extra properties to "cast" to the required wrapper type where you would normally do this with the Sitecore API - for example, the AsMultilistFieldField property on IField (which returns an IMultilistField)

Finally, I've demonstrated a few of my favourite isolation techniques for writing testable code which hooks into Sitecore's many pipelines. In the GitHub solution you'll see an example of a computed index field "ItemNamesConcatenatedComputedField". Here I've delegated pretty much all the functionality into a "helper" class called "ItemNamesConcatenated". The idea is that the processor class itself is pretty dumb and simple dispatches control to some code which is completely testable and pretty much isolated from Sitecore's API. Within "ItemNamesConcatenated" itself we use our wrapper classes pretty much in the same way as we'd use the Sitecore API:

    public class ItemNamesConcatenated
    {
        public string GetLinkedItemNames(IIndexable context, string sourceField)
        {
            if (context == null)
            {
                return string.Empty;
            }

            var item = context.AsItem();

            if (item.IsNull)
            {
                return string.Empty;
            }

            var field = item.Fields[sourceField].AsMultilistFieldField;
            if (field.IsNull)
            {
                return string.Empty;
            }

            CrawlingLog.Log.Info(string.Format("Indexed linked item field for {0}", item.Paths.FullPath));

            return string.Join(" | ", field.GetItems().Select(i => i.DisplayName));
        }
    }
    

The logging line is the only remaining reference to Siteore, and we could even wrap this if we wanted to. The unit tests are then super-simple, and hopefully the Builder factory classes and their fluent syntax make it easy to understand how are mocks are being set up - for example:

        [Test]
        public void GivenContextIsValid_GetLinkedItemNames_ReturnsCorrectValue()
        {          
            var fieldCollection = FieldCollectionBuilder.Create()
                .WithMultiListField("field1", new [] {"Value1", "Value 2","value3"})
                .Build();      
            var contextItem = ItemBuilder.Create()
                .WithFieldCollection(fieldCollection)
                .Build();
            var indexable = IndexableBuilder.Create()
                .WithItem(contextItem)
                .Build();

            var itemNamesConcatenated = new ItemNamesConcatenated();

            var result = itemNamesConcatenated.GetLinkedItemNames(indexable, "field1");
            Assert.That(result, Is.EqualTo("Value1 | Value 2 | value3" ));
        }

 

 


By James at 1 Nov 2015, 21:26 PM


Comments

Post a comment