This project is read-only.

unit testing correct decoration

Jun 14, 2013 at 5:28 PM
Edited Jun 14, 2013 at 5:29 PM
With the increasing number of decorators that are registered in the composition root, it becomes necessary to test the correct decoration of resolved instances.

How would I do that?

Example:
In my code base I have multiple decorators for command handlers:
  • Transaction
  • ModifiedEntitiesUpdater
  • FactoryProxy
How can I verify that all of them are actually getting applied and in the correct order?
Jun 14, 2013 at 11:18 PM
Edited Mar 2, 2014 at 6:38 PM
A few solutions come to mind:
  1. Use integration testing.
  2. (Ab)use the (internal) diagnostic API to query the relationships of a registration.
  3. Expose the decoratee as public property to allow verifying the correct wiring.
I think option 3 would be the simplest, but for the sake of completeness, let's discuss all three.

With integration testing we test the whole chain of objects in the system. A test that calls the container.Verify() method is in fact an integration test, but you can also test the complete pipeline by calling a decorated command. Still, it could be hard to verify whether a transaction is applied correctly. This would force the throwing of an exception in the middle of an operation and check whether all changes were rolled back. But still it's an option.

The InstanceProducer instances returned from GetRegistration and GetCurrentRegistrations contain a method called GetRelationships. It returns an array of KnownRelationship instances -after- you called container.Verify(). There will be an instance for each consumer-dependency relationship. Downside is that you will have to filter out all uninteresting relationships. But still it's an option :-)

Most obvious solution is to expose the decoratee as public property on the decorator. Since the decorator is added by the container, nobody can access those properties, but they will be available during unit testing. What you can do for instance is define the following interface:
public interface IDecorator<T>
{
    T Decoratee { get; }
}
Decorators can implement this interface:
public class TransactionDecorator<T> : ICommandHandler<T>,
    IDecorator<ICommandHandler<T>>
{
    public TransactionDecorator(ICommandHandler<T> decoratee)
    {
        this.Decoratee = decoratee;
    }

    public ICommandHandler<T> Decoratee { get; private set; }

    public void Handle(T command) { }
}
Although you can do this without this interface, the interface makes it easier to iterate through a chain of decorators with compile time support. You can for instance define the following helper method in your test suit:
private static List<Type> GetChainOfDecoratorTypes<T>(T instance)
{
    var chain = new List<Type>();

    var decorator = instance as IDecorator<T>;

    while (decorator != null)
    {
        chain.Add(decorator.GetType().GetGenericTypeDefinition());
        decorator = decorator.Decoratee as IDecorator<T>;
    }

    return chain;
}
With this, you can define tests like these:
[TestMethod]
public void AllCommandHandlersMustRunInATransaction()
{
    var handler = GetCommandHandlerFromContainer();

    var decoratorChain = GetChainOfDecoratorTypes(handler);

    Assert.IsTrue(decoratorChain.Contains(typeof(TransactionDecorator<>)));
}

[TestMethod]
public void InCaseOfADatabaseDeadlockANewTransactionMustBeCreated()
{
    var handler = GetCommandHandlerFromContainer();

    var decoratorChain = GetChainOfDecoratorTypes(handler);

    int deadlockIndex = decoratorChain.IndexOf(typeof(DeadlockRetryDecorator<>));
    int transactionIndex = decoratorChain.IndexOf(typeof(TransactionDecorator<>));

    Assert.IsTrue(decoratorChain.Contains(typeof(DeadlockRetryDecorator<>)));
    Assert.IsTrue(deadlockIndex < transactionIndex, 
        "In case of a deadlock a new transaction must be created.");
}
If you write your own assert methods you could make the assert more expressive and simple, but you'll get the idea.
Marked as answer by dot_NET_Junkie on 3/2/2014 at 10:38 AM
Jun 15, 2013 at 12:36 AM
Just wow. I really like those sample tests you showed! :-)
Many thanks, I will go with the third approach.