Concurrent Unit Tests with Service Locator

My talk at Microsoft Summit created a nice discussion with some of the participants about writing isolated unit tests when using the Service Locator.

It started from the part where I was showing how the AppBoot helps in dependencies management by enforcing consistency on how the Dependency Injection is done. With the AppBoot we make sure that Dependency Injection is used and that it is done only through the constructor. The question that started the discussion was:

Does this mean that I will have constructor parameters for everything? Including utility services like ILog? If so, it means that I will pollute the constructors with these details and…. things may get over complicated

My answer was that for logging or other similar utilities we could make static helpers that make them easier to be called. Such a helper would wrap a ServiceLocator, so we do not make a strong dependency on the logging implementation or library. Something like this:

public static class Logger  
{ 
    public static void Error(string headline, string message)  
    {  
        ILogSrv log = ServiceLocator.Current.GetInstance<ILogSrv>();  
        log.WriteTrace(new Trace(headline, message, Severity.Error));  
    }
    public static void Warning(string headline, string message)  
    {  
        ILogSrv log = ServiceLocator.Current.GetInstance<ILogSrv>();  
        …  
    }
    public static void Trace(string functionName, string message)  
    {  
        ILogSrv log = ServiceLocator.Current.GetInstance<ILogSrv>();  
        …  
    }
    public static void Debug(string message, object[] variables)  
    {   
        ILogSrv log = ServiceLocator.Current.GetInstance<ILogSrv>();  
        …  
    }  
}

This makes my class code to depend on some static functions (Logger.Error()), but that seems a good compromise as long as the underneath things remain as simple as in the above snippet.

Now, if we are to write some unit tests in isolation, we would like to use a stub for the ILogSrv interface, and we can do that by making a setup like this:

 [TestClass]  
 public class UnitTests  
 {  
   private Mock<IServiceLocator> slStub;

   [TestInitialize]  
   public void TestInitialize()  
   {  
     slStub = new Mock<IServiceLocator>();  
     ServiceLocator.SetLocatorProvider(() => slStub.Object);  
   }

   [TestMethod]  
   public void PlaceNewOrder_FromPriorityCustomer_AddedOnTopOfTheQueue()  
   {  
      Mock<ILogSrv> dummyLog = new Mock<ILogSrv>();
      slStub.Setup(l => l.GetInstance<ILogSrv>()).Returns(dummyLog.Object); 
      …  
   }  
    …  
 }  

This code configures the ServiceLocator.Current to return an instance which gives the dummy ILogSrv when needed. Therefore, the production code will use a dummy ILogSrv, which probably does nothing on WriteTrace().

For logging this may be just fine. It is unlikely that we would need different stub configurations for ILogSrv in different tests. However, things may not be as easy as this, for other services that are taken through the ServiceLocator.Current. We might want different stubs for different test scenarios. Something like this:

// — production code —
public class UnderTest  
{  
  public bool IsOdd()  
  {  
     IService service = ServiceLocator.Current.GetInstance<IService>();  
     int number = service.Foo();  
     return number%2 == 1;  
  }  
}

// — test code —
private Mock<IServiceLocator> slStub = new Mock<IServiceLocator>();  
ServiceLocator.SetLocatorProvider(() => slStub.Object);

[TestMethod]  
public void IsOdd_ServiceReturns5_True()  
{  
  Mock<IService> stub = new Mock<IService>();  
  stub.Setup(m => m.Foo()).Returns(5);

  slStub.Setup(sl => sl.GetInstance<IService>()).Returns(stub);  
  …  
}

[TestMethod]  
public void IsOdd_ServiceReturns4_False()  
{  
  Mock<IService> stub = new Mock<IService>();  
  stub.Setup(m => m.Foo()).Returns(4);

  slStub.Setup(sl => sl.GetInstance<IService>()).Returns(stub);  
 …  
}

Because our production code depends on statics (uses ServiceLocator.Current to take its instance), when these tests are ran in parallel, we will run into troubles. Think of the following scenario: Test1 sets up the slStub to return its setup for the IService stub. Then, on a different thread Test2 overwrites this setup and runs. After that, when the code exercised by Test1 gets the IService instance through the static ServiceLocator.Current it will receive the Test2 setup, therefore the surprising failure.

By default MS Test or VS Test will run tests from different test classes in parallel, so if we have more test classes which do different setups using the ServiceLocator.SetLocatorProvider(), we will run into the nasty situation that sometimes our tests fail on the CI server or on our machine.

So, what should we do?

One option is to avoid the dependencies to the statics and to get the service locator through Dependency Injection through constructor. This would make the above example like below:

 // — production code —

public class UnderTest  
{  
    private IServiceLocator sl;  
    public UnderTest()  
    {  
        sl = ServiceLocator.Current;  
    }

    public UnderTest(IServiceLocator serviceLocator)  
    {  
        this.sl = serviceLocator;  
    }

    public bool IsOdd()  
    {  
        IService service = sl.GetInstance<IService>();  
        int number = service.Foo();  
        return number%2 == 1;  
    }  
 }

// — test code —

[TestMethod]  
 public void IsOdd_ServiceReturns5_True()  
 {  
    Mock<IService> stub = new Mock<IService>();  
    stub.Setup(m => m.Foo()).Returns(5);

    Mock<IServiceLocator> slStub = new Mock<IServiceLocator>();  
    slStub.Setup(sl => sl.GetInstance<IService>()).Returns(stub);

    var target = new UnderTest(slStub.Object);  
    …  
 }

[TestMethod]  
 public void IsOdd_ServiceReturns4_False()  
 {  
    Mock<IService> stub = new Mock<IService>();  
    stub.Setup(m => m.Foo()).Returns(4);

    Mock<IServiceLocator> slStub = new Mock<IServiceLocator>();  
    slStub.Setup(sl => sl.GetInstance<IService>()).Returns(stub);

    var target = new UnderTest(slStub.Object);  
    …  
 }  

This would be a good solution and I favour it in most of the cases. Sometimes I add, as in the above snippet, a constructor without parameters that is used in the production code and one which receives the ServiceLocator as a parameter for my unit tests code.

The other option, which is the answer to the question at the start of the post, looks a bit more magical :). It fits the cases when we need and want to keep the simplicity the static caller brings. Here, we keep the production code as is and we make the unit tests to safely run in parallel. We can do this by creating one stub of the IServiceLocator for each thread and record it on a thread static field. We can do it with a ServiceLocatorDoubleStorage class that wraps the thread static field and gives the tests a clean way to setup and access it.

 public static class ServiceLocatorDoubleStorage  
 {  
  [ThreadStatic]  
  private static IServiceLocator current;

   public static IServiceLocator Current  
   {  
      get { return current; }  
   }

   public static void SetInstance(IServiceLocator sl)  
   {  
      current = sl;  
   }

   public static void Cleanup()  
   {  
      SetInstance(null);  
   }  
}  

Now, the unit tests will use the ServiceLocatorDoubleStorage.SetInstance() instead of the ServiceLocator.SetLocatorProvider(). So the test code from the above sample transforms into:

 [TestClass]  
 public class UnitTest  
 {  
   [AssemblyInitialize]  
   public static void AssemblyInit(TestContext context)  
   {    
        // the production code will get it through  
        // ServiceLocator.Current, so this is needed  
        ServiceLocator.SetLocatorProvider(  
            () => ServiceLocatorDoubleStorage.Current);  
   }

   private Mock<IServiceLocator> slStub;

   [TestInitialize]  
   public void TestInitialize()  
   {  
        slStub = new Mock<IServiceLocator>();  
        ServiceLocatorDoubleStorage.SetInstance(slStub.Object);  
   }

   [TestMethod]  
   public void IsOdd_ServiceReturns5_True()  
   {  
        Mock<IService> stub = new Mock<IService>();  
        stub.Setup(m => m.Foo()).Returns(5);

        slStub.Setup(sl => sl.GetInstance<IService>()).Returns(stub);  
        …  
   }  
   …  
 }  

With this, each time a new thread is used by the testing framework, the test on it will first set its own stub of the ServiceLocator and then it will run. This makes that the ServiceLocator stubs even if they are static resources, not to be shared among different tests on different threads. On the code samples from my Code Design Training, on github here, you can find a fully functional example that shows how this can be used and how it runs in parallel.

To conclude, I would say that Dependency Injection and Service Locator should be used together. I strongly push towards using Dependency Injection in most of the cases because it makes the dependencies clearer and easier to manage, but definitely there are cases where Service Locator is needed or makes more sense. In both cases writing isolated unit tests should be easy and may be a good check of our design and dependencies.

This is discussed and explained in detail in my Code Design Training
Featured image credit: noche via 123RF Stock Photo

Florin Coros

Read more posts by this author.