A (previously) difficult-to-test method in EPiServer
Consider the following method. It takes a page as a parameter and looks for a property on that page. If the property is set, it is simply returned. If it isn’t set, we return the value of a property from another page:
public static string GetAuthor(MyPageType page) { if (!string.IsNullOrWhiteSpace(page.Author)) { return page.Author; } // Fallback to company name set on start page ServiceLocator.Current.GetInstance<IContentRepository>().Get<StartPage>(ContentReference.StartPage).CompanyName; }
All of a sudden we have a dependency to EPiServer which (in the old days) would make this method quite difficult to unit test. So difficult in fact that we pretty much never did it… Luckily that has changed dramatically with EPiServer 7.
Create a new unit test class
I use MSTest quite a bit. It’s already included in most versions of Visual Studio, and it does a good enough job for me in many cases.
First, we add a new Basic Unit Test to our project:
A unit test class is like any other class, except that it has some extra test attributes:
Create a test method
Next we rename TestMethod1 into something more descriptive and do some very basic mocking to test our GetAuthor() method. I like the Arrange Act Assert pattern, which is basically just a way of structuring your unit tests:
[TestMethod] public void TestCompanyNameIsReturnedIfAuthorIsNotSet() { // Arrange, create a mock page with an empty Author property var page = new MyPageType { Author = null }; var startPage = ServiceLocator.Current.GetInstance<IContentRepository>().Get<StartPage>(ContentReference.StartPage); // Act, invoke the method we're testing var author = PageMetaData.GetAuthor(page); // Assert, we should have gotten the company name from the start page as a fallback value Assert.AreEqual(author, startPage.CompanyName); }
Now we see that both our test and the method we’re testing have dependencies to EPiServer, more specifically to IContentRepository.
If we try to execute our unit test now we’ll see an error like “Activation error occurred while trying to get instance of type IContentRepository” (which makes sense since EPiServer isn’t running):
Add a mocking framework to your test project
Mocking is the process of creating fake objects. Mocking frameworks commonly allow you to get concrete (but fake) implementations of interfaces, and then specify how those fake objects should behave (for example which values should be returned by their properties and methods).
One of my favorite mocking frameworks is Moq. To enable it for your tests, simply add the Moq NuGet package:
Mock IContentRepository for our unit tests
MSTest allows us to execute code to initialize tests, sort of an over-arching arrange for all test methods:
For our unit test we need a mock IContentRepository which can return a start page so that a method like the following method can execute in a unit test:
ServiceLocator.Current.GetInstance<IContentRepository>().Get<StartPage>(ContentReference.StartPage).CompanyName;
So, we add an initialization method decorated with the TestInitialize attribute to our test class. In this method we mock an IContentRepository and an IServiceLocator. Finally we tell EPiServer to use our fake service locator when resolving dependencies:
[TestInitialize] public void MockEPiServerDependencies() { // Create a mock repository var mockRepository = new Mock<IContentRepository>(); // Setup the repository to return a start page with a preset property value mockRepository.Setup(r => r.Get<StartPage>(ContentReference.StartPage)).Returns(new StartPage { CompanyName = "My company name" }); // Create a mock service locator var mockLocator = new Mock<IServiceLocator>(); // Setup the service locator to return our mock repository when an IContentRepository is requested mockLocator.Setup(l => l.GetInstance<IContentRepository>()).Returns(mockRepository.Object); // Make use of our mock objects throughout EPiServer ServiceLocator.SetLocator(mockLocator.Object); }
Now we can re-run our original unit test and - voilá:
Notice how we indeed got a start page with its CompanyName property! Now we can re-run our original unit test and see that it passes successfully: