I often hear people saying things like “Don’t use the ServiceLocator, you can’t write unit tests with it”, or “EPiServer‘s ServiceLocator will really cause problems in your unit tests”. It really will not though, and it’s quite easy to set up.
Simple base class for unit testing with EPiServer’s ServiceLocator
Quite often the argument is that the ServiceLocator’s container and your mocking framework’s container are two separate objects; while you mock stuff into the first one, your code looks for the stuff in the other one. This example will be using NUnit together with RhinoMocks to illustrate a simple set up.
TestBase.cs
using EPiServer.ServiceLocation; using NUnit.Framework; using Rhino.Mocks; // ... [TestFixture] public abstract class TestBase { private IServiceLocator _mockedServiceLocator; protected IServiceLocator MockedServiceLocator { get { if (_mockedServiceLocator != null) { return _mockedServiceLocator; } _mockedServiceLocator = MockRepository.GenerateMock<IServiceLocator>(); EPiServer.ServiceLocation.ServiceLocator .SetLocator(_mockedServiceLocator); return _mockedServiceLocator; } } public virtual T Using<T>() where T : class { var obj = MockRepository.GenerateMock<T>(); MockedServiceLocator.Stub(l => l.GetInstance<T>()).Return(obj); return obj; }
This is a smaller version of what a test base class may look like; all the superfluous stuff such as automocking removed. The interesting lines of code in the MockedServiceLocator property are 19-20. Since EPiServer is supplying a convenient SetLocator method under their ServiceLocator namespace, it is a simple thing to have RhinoMocks generate a mock of a ServiceLocator for us. Once we have a mock that we can work with, just have EPiServer use that instead of the real one.
The Using<T> method (named as such out of habit) generates and returns a mocked object of any class you want. Before it’s returned it also stubs the mock onto the mocked EPiServer ServiceLocator. In other words, if you call:
ServiceLocator.Current.GetInstance<ISomething>();
You will be using the MockedServiceLocator’s stub and get the mocked return object. You would go about it the same to stub other methods on the ServiceLocator, like GetAllInstances.
So if you are using the EPiServer ServiceLocator to get an IUrlResolver in your code, stubbing things on it may be done as below.
Using<IUrlResolver>() .Stub(s => s.Route(Arg<UrlBuilder>.Matches(arg => arg.ToString() == "/some/path"))) .Return(somePage);
Note that there may be reasons to refrain from using the ServiceLocator in an excessive an unstructured manner, but unit testing is hardly one of them. Sometimes it’s really a handy thing, like when you need to inject things in EPiServer ContentTypes for instance. If you try constructor injection in a PageType, things will not work well.
public class ArticlePage : PageData { private readonly Lazy<ISomeService> _someService = new Lazy<ISomeService>(() => ServiceLocator.Current.GetInstance<ISomeService>()); [Ignore] public virtual string SomeProperty => _someService.Value.SomeMethodUsing(this);
Injecting with the ServiceLocator in a lazy manner however, works like a charm (and yes, there are multiple reasons why you’d want to inject stuff into domain models; creating aggregated content for React.js is one of them).