Need Quality Code? Get Silver Backed

Given, When, Then - BDD Style Test Framework Part 2

13thFeb

0

by Gary H

Simplicity is the glory of expression.

Walt Whitman

As we covered in the first part of this series, the Leaping Gorilla testing framework sets out to remove as many sources of friction from the developer for testing as possible. In this article we'll dive into some of the mechanisms we use in order to make unit testing complex objects as easy as possible.

Composition, Composition, Composition

It's common programmer law to prefer Composition Over Inheritance. This means your relationships in code should be "Has A" rather than "Is A". This composition is bourne out in C# through the use of interfaces. It allows us to say "we use an object which has a way of retrieving a user" or "we use an object which has a way to write to a log". By moving our dependency from having the actual object to having an interface that guarantees we can perform the task we set ourselves up for phase two.

Dependency Injection and Inversion of Control

Let's take a simple class - we want to be able to perform a task and then write a message to the Log. Using our rule of composition we may write a class with a method to perform the action and use an interface to write to the Log like:

public class MyObject
{
	private ILogger Log { get; set; }

	public void DoAction(string actionMessage)
	{
			// Do something then...
		Log.Write("Did work! - {0}", actionMessage);
	}
}

Inversion of Control (IoC) allows us to say "we dont care what object you use to Log so long as it supports the ILogger interface". Dependency Injection (DI) addresses the "How" of how we assign a value to that Log property in the first place. There are many, MANY IoC and DI containers out there. All we care about is the semantics of saying I need a good way to get my dependency into my object.

To that end we at Leaping Gorilla prefer to use Constructor Injection. It is widely supported and it is a cleaner method of getting our dependencies into our class than property or setter injection.

Let's update our sample class with constructor injection:

public class MyObject
{	
	private ILogger Log { get; set; }

	public MyObject(ILogger log)
	{
		Log = log;
	}

	public void DoAction(string actionMessage)
	{
		// Do something then...
		Log.Write("Did work! - {0}", actionMessage);
	}
}

The Framework

The Leaping Gorilla Testing framework depends on both concepts of composition (coding to interfaces) and constructor injection to do its work. Lets take a look at how we might write a test for our sample class.

public class MyObjectTest : WhenTestingTheBehaviourOf
{	
	[ItemUnderTest]
	public MyObject ToTest { get; set; }

	[Dependency]
	public ILogger Log { get; set; }

	private string _actionMessage;

	[Given]
	public void WeHaveAnActionMessage()
	{
		_actionMessage = "Test Message";
	}

	[When]
	public void WeCallDoAction
	{
		ToTest.DoAction()
	}

	[Then]
	public void TheLogShouldBeWrittenTo()
	{
		Log.Received().Write(Arg.Any<string>(), _actionMessage);
	}
}

The test runs and green lights. How does the logger get injected into the object we are testing? That's where the framework comes in.

Reflections on Reflection

We use reflection. This allows us to inspect an object at runtime and discover information about it to act upon. In the case of the testing framework we start by finding all of the properties and fields which are marked with a [Dependency] attribute.

private IEnumerable<PropertyInfo> GetPropertiesWithAttribute(Type attributeType)
{
	return GetType().GetProperties(BindingFlags.Public | 
									BindingFlags.NonPublic | 
									BindingFlags.Instance)
			.Where(prop => prop.IsDefined(attributeType, false));
}

Next we find the [ItemUnderTest] and inspect its constructors. We attempt to find the constructor that best fits the dependencies that we have available to us.

private static ConstructorInfo GetPreferredConstructor(
	Type itemUnderTestType, 
	IReadOnlyCollection<Dependency> dependencies)
{
	var constructors = itemUnderTestType.GetConstructors();

	var preferredConstructor =
		constructors.FirstOrDefault(ci =>
		{
			var pars = ci.GetParameters();
			return pars.Count() == dependencies.Count &&
				   pars.All(p => 
				   	dependencies.Any(d => 
				   		d.Name == p.Name 
				   		&& d.Type == p.ParameterType));
		})

		??

		constructors.FirstOrDefault(ci =>
		{
			var pars = ci.GetParameters();
			return pars.Count() == dependencies.Count &&
				   pars.All(p => 
				   	dependencies.Any(d => 
				   		d.Type == p.ParameterType));
		});

	return preferredConstructor;
}

Finally we use this to create the item under test and we load it into the class itself.

var parameters = constructor.GetParameters();
var constructorArguments = new object[parameters.Length];

for (int index = 0; index < parameters.Length; index++)
{
	var param = parameters[index];
	var dep = dependencies
				.FirstOrDefault(d => d.Name == param.Name 
					&& d.Type == param.ParameterType) 
			?? 
			dependencies
				.FirstOrDefault(d => d.Type == param.ParameterType);

	if (dep == null)
	{
		throw new DependencyMismatchException(
			param.ParameterType, 
			param.Name, index, parameters);
	}

	dependencies.Remove(dep);
	constructorArguments[index] = dep.Value;
}

accessor[this, itemUnderTest.Name] = 
	Activator.CreateInstance(itemUnderTest.Type, 
							constructorArguments);

Conclusions

If you are interested in investigating further then we encourage you to check out the GitHub Repository.

C# , Testing

Comments are Locked for this Post