allBlogsList

insite-unit-testing

Introduction

Unit testing requires a ground up approach. Fortunately, Insite's amazing architecture gives us the ability to unit test our Insite solutions by their use of [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection "Dependency Injection"). However, custom code needs to adhere to the practices they've established:

  • Use Dependency Injection - Inversion of Control is the rule. Custom services and injectables should implement an interface that implements Insite's IDependency interface. Insite also includes 3 derived interfaces that govern life-cycle management or special injection cases. Be sure to implement your custom dependencies with one of these interfaces.
    • IDependency - The root interface for dependencies. By default, Insite uses their custom PerRequestLifetimeManager. Instances are reused within an HTTP request, but not shared between requests.
    • ISingletonLifetime - Ensures only a single instance is reused. Specifically, Insite designates the Unity [ContainerControlledLifetimeManager](https://msdn.microsoft.com/en-us/library/ff660872(v=pandp.20).aspx "Understanding Lifetime Managers") for the life-cycle management of the type.
    • ITransientLifetime - Ensures a new instance is returned every time the interface is requested. Specifically, Insite designates the Unity [TransientLifetimeManager](https://msdn.microsoft.com/en-us/library/ff660872(v=pandp.20).aspx "Understanding Lifetime Managers").
    • IInterceptable - Special interface used for cross cutting concerns. See Microsoft's article about [Unity Interceptors](https://msdn.microsoft.com/en-us/library/dn178466(v=pandp.30).aspx "Interception using Unity").
  • Use Static Providers - Insite has a few static references for simple, frequently used, systems. A couple key examples to this are dates/times and local files. Most of these are shipped with their own mock implementations.
    • DateTimeProvider - The default implementation wraps DateTime.Now. In unit tests, we don't necessarily want the actual time they are run. Instead, we want to inject a value we can compare against. Be sure to use DateTimeProvider.
    • PathProvider - The default implementation wraps HostingEnvironment.MapPath(). This handles getting the path to local files.
    • AppSettingProvider - A quick way to get settings from the ApplicationSetting table, in the Insite database.
    • MessageProvider - A quick to get application messages from the ApplicationMessage table, in the Insite database.

Dependency Mocking Unity Extension

For those who aren't familiar with Insite's implementation of dependency injection, it uses the [Microsoft's Unity](https://msdn.microsoft.com/en-us/library/ff647202.aspx "Unity Container"). Through the magic of Insite's DependencyRegistrar, all the classes that implement the IDependency interface are registered with the Unity container. When dependent classes are instantiated, Unity injects any dependencies into the constructor of the classes.

When unit testing, however, we don't want to use the dependencies that Insite automatically registers. Instead, we want to inject mock classes into the unit under test. By doing this, we describe a desired environment to our unit under test, through the interfaces of our mocked dependencies. In turn, we expect it to behave a specific way to the facts that it receives. NSubstitute is capable of creating dynamic proxies. In fact, it goes one step further, to make our lives easier - [auto and recursive mocks](http://nsubstitute.github.io/help/auto-and-recursive-mocks/ "Auto and recursive mocks") fill in some of the blanks that we don't care about. They help us avoid some annoying null reference exceptions that might otherwise occur.

In order to configure Unity to return NSubstitute mocks, we create a Unity extension. There's a catch. Unity prefers to handle instantiation. NSubstitute only produces instances of mocks. To mitigate the differing approaches, we need to create a Unity BuilderStrategy. It will preempt Unity's trying to build objects that we want to mock. Specifically, this occurs in the BuilderStrategy.PreBuildUp implementation, below.

One more catch to our implementation - we want to register our mock behaviors prior to running the unit under test. This functionality is exposed through the AddTransformation and AddTransformations methods of the container extension. If the mock instance is already registered with the container, then we apply the NSubstitute transformation that describes the behavior of the mock. If the mock doesn't exist, we save a concatenation of transformations to apply to the mock, when it is requested.

AutoMockingExtension.cs

public class AutoMockingExtension : UnityContainerExtension
{
	private AutoMockingBuilderStrategy _buildingStrategy;

	public IReadOnlyDictionary<Type, object> KnownInstances => _buildingStrategy.GetKnownInstances();

	public delegate ConfiguredCall MockTransformation(object mock);

	public delegate ConfiguredCall MockTransformation<in TInterface>(TInterface mock) where TInterface : class, IDependency;
	
	protected override void Initialize()
	{
		_buildingStrategy = new AutoMockingBuilderStrategy(Container);
		Context.Strategies.Add(_buildingStrategy, UnityBuildStage.PreCreation);
	}

	public void AddTransformation<TInterface>(MockTransformation<TInterface> transformation)
		where TInterface : class, IDependency
	{
		_buildingStrategy.AddTransformation(transformation);
	}

	public void AddTransformations<TInterface>(params MockTransformation<TInterface>[] transformations)
			where TInterface : class, IDependency
	{
		foreach (var transformation in transformations)
		{
			AddTransformation(transformation);
		}
	}
	
	private class AutoMockingBuilderStrategy : BuilderStrategy
	{

		private readonly IUnityContainer _unityContainer;
		private readonly Dictionary<Type, object> _knownInstances;
		private readonly Dictionary<Type, MockTransformation> _transformations;

		public AutoMockingBuilderStrategy(IUnityContainer unityContainer)
		{
			_unityContainer = unityContainer;
			_knownInstances = new Dictionary<Type, object>();
			_transformations = new Dictionary<Type, MockTransformation>();
		}

		public override void PreBuildUp(IBuilderContext context)
		{
			var key = context.OriginalBuildKey;

			if (key.Type.IsInterface && !_unityContainer.IsRegistered(key.Type))
			{
				if (!_knownInstances.ContainsKey(key.Type))
				{
					_knownInstances[key.Type] = CreateSubstitute(key.Type);
					
				}
				context.Existing = _knownInstances[key.Type];
			}
		}

		public void AddTransformation<TInterface>(MockTransformation<TInterface> transformation) 
			where TInterface : class, IDependency
		{
			// If the instance is already known, apply the transformation to it.
			if (_knownInstances.ContainsKey(typeof(TInterface)))
			{
				transformation((TInterface) _knownInstances[typeof(TInterface)]);
			}

			// Update the transformations, in case the instance isn't known yet.
			if (!_transformations.ContainsKey(typeof(TInterface)))
			{
				_transformations[typeof(TInterface)] = mock => transformation(mock as TInterface);
			}
			else
			{
				// Method for concatenating (chaining) lambda expressions.
				// See: https://stackoverflow.com/questions/491780/concatenating-lambda-functions-in-c-sharp
				_transformations[typeof(TInterface)] += mock => transformation(mock as TInterface);
			}
		}
		
		private object CreateSubstitute(Type type)
		{
			var mock = Substitute.For(new[] {type}, null);
			if (_transformations.ContainsKey(type))
			{
				_transformations[type](mock);
			}
			return mock;
		}

		public IReadOnlyDictionary<Type, object> GetKnownInstances()
		{
			return new ReadOnlyDictionary<Type, object>(_knownInstances);
		}
	}
}
		

Working with Insite's Static Providers

  • PathProvider - However, consumers typically use the AspNet root folder tilde (~), when referring to paths within the website directory. When we're unit testing, tilde doesn't mean anything.

Simplify Testing Activities

Examples