Unit Testing Sitecore Components Part 5: Recap and Resources
This is the final post in my “Unit Testing Sitecore Components” series. In this series I’ve taken a seemingly simplistic Sitecore component and refactored it by applying several principals to make the code more reusable and testable. In this final post of the series I’ll recap the principals and provide a few resources to help explain them further.
This post is the final in 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, along with the principals covered in the post:Post not found: logicless-view-itemless-model
- Principal: Keep business logic out of the view.
- Principal: Keep
Itemout of the model.
- Principal: Encapsulate logic.
- Principal: Use Dependency Injection and Abstractions
- Principal: Avoid implicit data
- Principal: Use Sitecore Abstractions
- How to mock the
- How to mock field values of a mocked item.
This is a quick recap of the principals I’ve covered in this series. You’ll find more detail for each in the posts listed above.
Business logic burried inside a view can only be used inside that view. This is means the logic cannot be reused and generally makes unit testing quite difficult. To allow the business logic to be reused and properly unit tested, the logic must be kept in some other place outside of the view. Keep the view purely for presentation to transform from your model to the markup required for your channel (HTML).
With the APIs Sitecore offers and the prevelant use of the
Item class, it may feel natural to use an item instance as the model in your project. Patterns such as the Post not found: custom-item-pattern Custom Item Pattern also contribute to this confusion as custom items are a kind of model. The fact that the
Item class is now easily mockable means unit testing is no longer a concern for this principal. But in production code, items cannot be instantiated directly. They must always be loaded from a database. Dependeing on how you use the item instances, this can cause performance issues at scale. It also means your model can never be provided from any other location other than a Sitecore database.
This principal is closely related to the “Keep business logic out of the views” principal above. To ensure logic can be reused, it must be encapsulated so it can be shared between components. Encapsulating logic also makes it easier to test the logic in isolation of any concerns of where it is used.
This principal possibly has the biggest impact on how complicated your unit tests will end up being. If a components dependencies cannot be specified at test-time, then those dependencies cannot be controlled and mocked. This principal ties back to the good old Object Oriented principal of “code to an abstraction” which specifies that classes should not contain references to concrete implementations, but only to abstractions. This makes changing the dependency much easier, whether that be a new dependency in production code or switching a real implementation to a test mock for testing purposes.
Related to the above principal, make a classes dependencies explicit and clear. That will make the class much more reusable as the logic of the class is no longer tied to the implicit data and whatever it holds. Implicit data can also complicate tests, especially if that implicit data cannot be directly set.
Sitecore contains a large number of static classes which provide access to various servies. These services are all now registered with the service provider as an appropriate abstraction. This allows code to follow the “Use Dependency Injection and Abstractions” principal and avoid making implicit service calls, which may fail in a test context. By using the appropriate Sitecore abstractions for things like manager classes, it makes it much easier to mock the class and inject it for tests.
The Post not found: mocking-items-and-fields post of this series covered some techniques which rounded out the other posts and showed how to complete the unit tests for the sample component I was refactoring.
Ever since the Sitecore abstractions updates which were first introduced in Sitecore 8.2, Post not found: sitecore-8-2-mock-your-items mocking an item has been relatively easy.
Using NSubstitute to create mocks:
var db = Substitute.For<Database>();
Fields can be mocked depending on how they’re accessed in the calling code.
For simple item indexer access (assuming
item is an NSubstitute mock):
item["field name"].Returns("field value");
Other field access approaches (like using the
Fields property of the
Item) are covered in the Post not found: mocking-items-and-fields post.
I’ve created a repository on github containing the code I used in my presentation. You’ll find it at https://github.com/adeneys/unit-testing-sitecore-components. The initial commit includes the component which I refactored during my presentation in it’s initial (bad) state. I tagged each commit to explain the actions that were taken in that commit, so you can walk through the refactoring process.
To start at the first commit, after cloning the repository, simply check out the
git checkout 0-start
Walk through the tags to watch how the code changes as the principals are applied:
git checkout 1-add-tests git checkout 2-itemless-model git checkout 3-controller-move-logic git checkout 4-extract-logic git checkout 5-refactor-controller git checkout 6-refactor-view
The tags (and commits) don’t match 1 to 1 with the principals covered in this series. This is because several princiapls were applied at once during a single logical change.
If you’d like to see exactly how I applied these principals during my SUGCON presentation, the good folks who organised the whole event managed to record the sessions and my full presentation is available on YouTube at https://www.youtube.com/watch?v=Gja_bAV15HM.
In this series I wanted to emphasize that unit testing Sitecore components is more than just mocking items and fields. A little bit of design can both reduce the complexity of the test suite and allow more of the code to be reused.