Unit Testing Sitecore Components Part 3: Avoid Static Members
In the posts of this series so far, I’ve refactored a sample view rendering to make the rendering itself unit testable. In doing do I’ve extracted the business logic to a separate class which still needs some work before it can be properly tested. In this post, I’ll be covering 2 more principals to help with making Sitecore components more testable and reusable.
This post is part of a series covering the principals I showed during my virtual SUGCON presentation on unit testing Sitecore components in April of this year (2020). The following is a list of the posts belonging to the series so far, along with the principals covered in the post:Unit Testing Sitecore Components Part 1: Logicless Views and Itemless models
- Principal: Keep business logic out of the view.
- Principal: Keep
Itemout of the model.
- Principal: Encapsulate logic.
- Principal: Use Dependency Injection and Abstractions
In this post, I’ll cover 2 more principals:
- Principal: Avoid implicit data
- Principal: Use Sitecore Abstractions
Implicit data is data which the class accesses without it being passed into the class explicitly. A standard ASP.NET example of implicit data would be anything from the
HttpContext like query string values or session state. A Sitecore example of implicit data would be anything from
Sitecore.Context. This member is static and any class can easily access it without it being passed in.
The issue with using implicit data is that it tightly couples the code to that specific data, and accessing the data only in that way. I can’t reuse that code to work with data from a different source. If my code is accessing the context item through the
Sitecore.Context.Item property, then I can’t then use that same code to work with items which I read from the content tree, or which are returned from Content Search.
Let’s take a look at an example.
public string GetItemTitle()
The above code is tightly coupled with the
Sitecore.Context class and it can only provide the title of the context item. This also makes testing the code more difficult, as I now need to set the
Sitecore.Context.Item property to the item I want to use in the test.
If I avoid the implicit data (the context item) and instead pass the item in, then this code becomes much more reusable and easier to test:
public string GetItemTitle(Item item)
If you’ve read the previous posts in this series, you’d note that the
EntryTaxonomy class from the examples already avoids implicit data. In case you’ve not read those posts, here’s the current implementation of that class:
public class EntryTaxonomy : IEntryTaxonomy
The item to retrieve the categories for is already being passed in as a parameter. This is a side-effect of the original example rendering being a View Rendering. View Renderings in Sitecore already help with breaking this anti-pattern by providing an
Item property on the
RenderingModel which is set either by the context item or the specified data source.
But if I’m avoiding implicit data, how do I determine the context item? Like all rules, there are exceptions. And in this case, the exception would be the entry points to your code. Constrain usage of implicit data to the entry points only. Keep your web entry points focused on pure web concerns; parsing requests, calling out to the appropriate isolated logic and preparing a web friendly response. Using
Sitecore.Context.Item or even
HttpContext.Current.Request.QueryString is perfectly fine inside a
RenderingModel derivative or a controller. This data is concerned with the current request so can terminate at these entry points, but don’t let it leak beyond.
There is one last refactoring I need to make to the
EntryTaxonomy class before it’s ready to be tested. Currently, the class is using the static
LinkManager.GetItemUrl() method. In many other systems the use of static members would render this code very difficult, if not impossible to test. This is because I can’t switch out the real implementation for a mock which I can easily validate. Sitecore does allow for switching the concrete
LinkProvider implementation which the
LinkManager calls, but it’s more complex than it needs to be.
Instead, we’ll use the available Sitecore abstractions of the manager classes along with dependency injection to pass in the link manager implementation:
public class EntryTaxonomy : IEntryTaxonomy
Now I am able to pass in a mocked
BaseLinkManager in my unit tests and control it’s behaviour. And to make this work with the
EntryCategoriesRenderingModel class from the previous post examples I don’t have to do anything. Sitecore dependency injection doesn’t jsut take care of the immediate dependencies into my code, it also handles dependencies for the dependencies. So it will pass the registered
BaseLinkManager implementation when it instantiates my class.
Avoiding implicit data makes code more reusable and makes testing it much easier. By using Sitecore Abstractions rather than convenience static methods, I’ve prepared the code for testing.
With the final refactorings now in place, I’m ready to write the tests for the
EntryTaxonomy class, which I’ll do in the next post.