Source code
Introduction
xUnit
is widely used testing library and the post is devoted to the writing unit tests for classes which use static properties or shared resources. Several scenarios are considered in the article.
Background
Solution uses C#7, .Net 4.7.2, NuGet packages Unity and CommonServiceLocator to implement ServiceLocator
pattern, and NuGet packages xUnit, Moq, FluentAssertions and Ikc5.TypeLibrary to write tests.
Issue
Let’s assume that the application has classes which use a shared resource like the static property or the shared container. Definitely there are examples of bad code patterns and the best way is to update code in the project by using DI and removing static properties at all. Unfortunately there is could be not enough time for such changes or this is a legacy application and the writing tests is a the first step to improve the project.
Good written unit tests should initialize necessary data and resources, call test routine, assert what is expected, and clear resources. In addition it is expected to use the minimal set of required dependencies and resolved it to mocked instances whenever it is possible. By default xUnit framework runs tests in parallel, so in the case of shared resources several tests could simultaneously change state or access shared resource that leads to the case when some tests get unexpected value and fail. Let’s note that at the same time each single test is successful, and fails only when all tests are run. The following image demonstrates this behavior:
According to the scheme solution could be searched in the following ways:
- disable parallelization feature and run test sequentially;
- change code to remove call to the shared resource from tested classes;
- keep parallelization feature but add synchronization between test threads.
Let’s discuss it below.
In this post we consider two code examples – the class with value type static property and implementation of service locator pattern. ServiceLocator
is one of widely used patterns for implementing dependency injection. Container is filled up with dependencies, and application classes use static instance of IServiceLocator
to call Resolve
or GetInstance
methods to resolve dependencies.
Solution
Console application
Provided solution contains ConsoleApp
console application and several ConsoleApp.*Tests
test libraries that demonstrate considered approaches. Classes and test classes are arranged by folders Resource
and Locator
depends on the implemented case. The Program.Main
method initializes static property and Unity container and calls client classes’ methods.
Static property
ConsoleApp
contains resource class that exposes static property SharedProperty
public class Resources
{
public string InstanceProperty { get; set; } = "Instance value";
public static int SharedProperty { get; set; } = 17;
}
and client class that consumes it
public class Class1
{
// … other methods
public string GetValues()
{
return Resources.SharedProperty.ToString("D3");
}
}
ServiceLocator pattern
ConsoleApp
contains three similar interfaces with single method GetValue
, corresponding implementation classes where this method returns string constant, and two classes such that each consumes two of three services.
The interface IService
and its implementations Service
are listed below
public interface IService1
{
string GetValue();
}
public class Service1 : IService1
{
public string GetValue()
{
return "Service1";
}
}
In order to implement ServiceLocator
pattern, application contains UnityServiceLocator
class that is derived from ServiceLocatorImplBase
with default implementation.
public class UnityServiceLocator : ServiceLocatorImplBase
{
private readonly IUnityContainer _unityContainer;
public UnityServiceLocator(IUnityContainer unityContainer)
{
_unityContainer = unityContainer;
}
protected override object DoGetInstance(Type serviceType, string key)
{
return _unityContainer.Resolve(serviceType, key);
}
protected override IEnumerable DoGetAllInstances(Type serviceType)
{
return _unityContainer.ResolveAll(serviceType);
}
}
Further, the application contains two client classes Class1
and Class2
with GetServiceValues
method that returns a collection of values from two services: Service1
and Service2
for Class1
, and Service2
and Service3
for Class2
by resolving service instances via service locator. It is used to demonstrate different required services for Class1Tests
and Class2Tests
tests.
//Locator.Class1.cs
public class Class1
{
// … other methods
public IEnumerable GetServiceValues()
{
var service1 = ServiceLocator.Current.GetInstance();
yield return service1.GetValue();
var service2 = ServiceLocator.Current.GetInstance();
yield return service2.GetValue();
}
}
Test library
Let’s consider ConsoleApp.FailedTests
test library. It contains tests for both cases separated to folders Resource
and Locator
. According to the library name, these tests demonstrates the main issue – each single test is successful, but two of them, one per case, are always fail when all tests are run.
Static property tests
The library contains Collection1Tests
and Collection2Tests
classes with tests for client class that uses static property. All tests look in the similar way
//Resource.Collection1Tests.cs
[Fact]
public void Class1_ShouldReturn_InitValue()
{
const string expectedValue = "017";
Resources.SharedProperty = 17;
Thread.Sleep(100);
var class1 = new Class1();
// test routine
var actualValue = class1.GetValues();
// assert
actualValue.Should().NotBeNullOrEmpty();
actualValue.Should().Be(expectedValue);
}
Let’s note that tests include the statement Thread.Sleep(100)
to emulate some set up work.
Service Locator tests
Similarly, the library contains Class1Tests
and Class2Tests
classes with tests for client classes that use service locator.
//Locator.Class1Tests.cs
[Fact]
public void Class1_ShouldReturn_TwoServiceMockValues()
{
const string expectedService1Value = "Service1Mock";
const string expectedService2Value = "Service2Mock";
// set services
var service1 = Mock.Of(mock => mock.GetValue() == expectedService1Value);
var service2 = Mock.Of(mock => mock.GetValue() == expectedService2Value);
// set container
var container = new UnityContainer();
container
.RegisterInstance(typeof(IService1), service1, new ExternallyControlledLifetimeManager())
.RegisterInstance(typeof(IService2), service2, new ExternallyControlledLifetimeManager());
var locator = new UnityServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => locator);
// test routine
var class1 = new Class1();
var values = class1.GetServiceValues()?.ToList();
// assert
values.Should().NotBeNull();
values.Count.Should().Be(2);
values[0].Should().Be(expectedService1Value);
values[1].Should().Be(expectedService2Value);
}
Approaches
There are different approaches to solve the issue, but each of them has advantages and disadvantages. Let’s analyze how code or tests could be changed.
Control test order
According to Running Tests in Parallel documentation, by default xUnit runs tests in parallel. To remove simultaneous calls from tests to the shared resource, test classes could be updated in the following way:
Keeping all tests that use shared resource in the same collection by adding
CollectionAttribute
attribute to the test classes.ConsoleApp.CollectionTests
library demonstrates this approach:[Collection("Resource Collection")] public class Class1Tests() { // ... }
and
Simultaneously, tests in[Collection("Resource Collection")] public class Class2Tests() { // ... }
Locator
folder are marked by the attribute[Collection("Locator Collection")]
;- Put all tests that use the same shared resource to the same test class. Due to this tests will be included to the same collection automatically and runs sequentially;
- Separate tests to different assemblies.
After such changes all tests are successful. The main disadvantage of this approach is obvious – usually real-world application contains a lot of unit tests and executing them sequentially could dramatically increase total time of the execution. Moreover, tested method could use more then one shared resource that complicates separating to several collections.
Update code to use dependencies
The issue could be solved if remove calls to shared resources from classes and add middle layer such that it allows different implementation: implementation with calls to shared resources in real application and mocked object in test environment. The main idea is to add dependencies to client class, that is instance or interface of middle layer class. Various implementations are possible, and let’s consider some of them, that is implemented in ConsoleApp.FixedCodeTests
.
Static property tests
Implement IResourcesWrapper
interface that repeats public properties and methods of Resources
class
public interface IResourcesWrapper
{
string InstanceProperty { get; set; }
int SharedProperty { get; set; }
}
and add constructor parameter to the client class
// Resource.Class1.cs
public class Class1
{
private readonly IResourcesWrapper _resourcesWrapper;
// dependency injection in constructor
public Class1(IResourcesWrapper resourcesWrapper)
{
_resourcesWrapper = resourcesWrapper;
}
public string GetValues()
{
// replace call to static property by the call to property of the instance
return _resourcesWrapper.SharedProperty.ToString("D3");
}
}
In the application, new class ResourcesWrapper
is created and it implements IResourcesWrapper
and returns value of static property
public class ResourcesWrapper : IResourcesWrapper
{
private readonly Resources _resources = new Resources();
// … other methods and properties
public int SharedProperty
{
get => Resources.SharedProperty;
set => Resources.SharedProperty = value;
}
}
On the other hand, tests are changed accordingly, i.e. create mock of IResourcesWrapper
and set static property to expected value
[Fact]
public void Class1_ShouldReturn_InitValue()
{
// ...
var resourcesWrapper = new Mock();
resourcesWrapper.SetupGet(mock => mock.SharedProperty).Returns(17);
var class1 = new Class1(resourcesWrapper.Object);
// test routine - the same
// ...
}
This approach works for static methods and static classes, too.
In addition, the similar solution could be implemented without interface, where instance of ResourceWrapper
class is injected to Class1
class constructor. Then in the test library, a developer could create derived class TestSharedResourceWrapper
that returns test value or mocked object. But previous implementation is better according to dependency inversible principle.
Service Locator tests
Analyze code and find out that shared resource returns object that implements some interface. For example, in the case of service locator implementation, ServiceLocator.Current
is an object that implements IServiceLocator
interface that could be injected to the application classes. To implement this idea, let’s update Class1
:
// Locator.Class1
public class Class1
{
private readonly IServiceLocator _serviceLocator;
public Class1(IServiceLocator serviceLocator)
{
_serviceLocator = serviceLocator;
}
public IEnumerable GetServiceValues()
{
var service1 = _serviceLocator.GetInstance();
yield return service1.GetValue();
// ...
}
}
Then tests are changed slightly, i.e. pass IServiceLocator
instance to the class constructor
[Fact]
public void Class1_ShouldReturn_TwoServiceMockValues()
{
// set container and services - as before
// ...
var locator = new UnityServiceLocator(container);
var class1 = new Class1(locator);
// test routine - the same
// ...
}
Now each class uses its own copy of container without accessing it via static property.
Summarizing
Again, after these changes all tests are successful. The main advantage of this approach is that it is one more step toward loose class coupling and applying dependency injection pattern. The disadvantages of this approach are also quite obvious – it could be hard to implement and it requires a lot of code changes as usually real-world application contains many classes with probably complex hierarchy. Constructors could be restricted for modifications, added dependencies could dramatically increase number of constructor parameters, and changes affect other classes. In addition, often business is not ready for additional development time just for writing unit tests.
Synchronize test threads
Another solution is to synchronize access of tests to the shared resources, as is shown in ConsoleApp.SynchronizedTests
. The main idea is to use EventWaitHandle
to control access to the shared resource. Before test runs the tested method, it waits for signaled state of EventWaitHandle
instance, sets unsignaled state, uses shared resource, and then sets signaled state back as soon as possible. Let’s consider implementation of this approach in our cases.
Static property tests
Let’s create class which wraps event wait handle. Each instance opens handle by its name, which is stored in static property. Let’s use generic class as it allows to keep unique value of event wait handle name per combination of type arguments. Method Lock
waits for EventWaitHandle
and throw an exception after fixed wait time if event wait handle doesn’t send a signal. Dispose
method set handle to signaled state and frees resources.
public class SynhronizeTest : DisposableObject
{
/// ...
private EventWaitHandle EventWaitHandle { get; set; }
private static string EventWaitHandleName { get; }
/// ...
private static EventWaitHandle GetEventWaitHandle()
{
if (EventWaitHandle.TryOpenExisting(EventWaitHandleName, out var eventWaitHandle))
return eventWaitHandle;
// TRUE to set signaled state that allows the first thread continue
return new EventWaitHandle(true, EventResetMode.AutoReset, EventWaitHandleName);
}
public void Lock()
{
EventWaitHandle = GetEventWaitHandle();
if (!EventWaitHandle.WaitOne(TimeSpan.FromMilliseconds(WaitTime)))
throw new ArgumentException(/*…*/);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (EventWaitHandle != null)
{
EventWaitHandle.Set();
EventWaitHandle.Close();
EventWaitHandle = null;
}
}
base.Dispose(disposing);
}
}
Then tests should create the instance of SynhronizeTest
and disposes it after tested method have been executed.
[Fact]
public void Class1_ShouldReturn_InitValue()
{
// ...
using (var synchronizeTest = new SynhronizeTest())
{
synchronizeTest.Lock();
Resources.SharedProperty = 17;
// test routine - the same
// ...
}
// assert - the same
// ...
}
Service Locator tests
For the case of service locator pattern let’s use the similar approach. At first, create class TestableServiceLocator
that is not a generic class, keep unity container in class property and set service locator inside Lock
method.
public abstract class TestableServiceLocator : DisposableObject
{
/// ...
private EventWaitHandle EventWaitHandle { get; set; }
protected IUnityContainer Container { get; private set; }
private static string EventWaitHandleName { get; }
/// ...
public void Lock()
{
EventWaitHandle = GetEventWaitHandle();
if (!EventWaitHandle.WaitOne(TimeSpan.FromMilliseconds(WaitTime)))
throw new ArgumentException(/*...*/);
var locator = new UnityServiceLocator(Container);
ServiceLocator.SetLocatorProvider(() => locator);
}
// ...
}
As there are set of tests that requires the same container, let’s derive classes from TestableServiceLocator
that keeps defined container:
internal class Class1ServiceLocator : TestableServiceLocator
{
// ...
public Class1ServiceLocator()
: base(new UnityContainer())
{
// set services
var service1 = Mock.Of(mock => mock.GetValue() == Service1Value);
// ...
// set container
Container
.RegisterInstance(typeof(IService1), service1, new ExternallyControlledLifetimeManager())
// ...
}
// ...
}
As the last step, tests should create the instance of this derived class, runs tested method and dispose locking instance.
[Fact]
public void Class1_ShouldReturn_TwoServiceMockValues()
{
IList values;
using (var serviceLocator = new Class1ServiceLocator())
{
serviceLocator.Lock();
// test routine - the same
// ...
}
// assert - the same
// ...
}
Summarizing
Again, after these changes all tests are successful. The main advantage of this approach is that it is doesn’t require changes of the application and all things are done in test library. In addition, this approach is good for large number of unit tests and in some kind is compromise solution between previous solutions. On the other hand, additional time is added due to thread synchronization, so it could be considered as disadvantage of this approach.
Conclusions
Writing unit tests that use shared resource is a challenge and in this post we consider various approaches how to solve possible issues. Each method has it disadvantages and advantages and the decision which method to use depends on various factors including business needs, complexity of systems, used tools.
1. All used IP-addresses, names of servers, workstations, domains, are fictional and are used exclusively as a demonstration only.
2. Information is provided «AS IS».