Thursday, 2 December 2010

Verifying that Windsor is configured correctly

Yesterday I experienced some very weird behaviour in an MVC app I am building - and it all came down to me being sloppy when configuring Windsor to resolve instances of HttpContextBase. In my haste I'd left the lifestyle configuration as default, rendering the resolved HttpContextWrapper as a singleton. That's not particularly useful.

When this happened it occurred to me that it would be nice to be able to verify that you've configured Windsor in the correct way and that you haven't inadvertently changed that configuration during development. So I knocked up a couple of extension methods on IWindsorContainer that can help. With these you can run tests such as:


_container.ShouldMap<IDisposable>().To<MyClass>();
_container.ShouldMap<IDisposable>().To<MyClass>().WithLifeStyle(LifestyleType.PerWebRequest);
_container.ShouldMap<HttpContextBase>().WithLifeStyle(LifestyleType.Singleton);

All you need is an instance of IWindsorContainer and the extension methods below. Note, that if you're testing stuff that's environment dependent (i.e. resolving HttpContext which is dependent on the ASP.NET runtime) you'll have to set that up separately. Lastly, I've built this against NUnit - but refactoring to other frameworks should be easy.

public static class ContainerExtensions
{
public class ComponentWrapper
{
public IWindsorContainer Container { get; set; }
public ComponentModel Component { get; set; }
}

public static ComponentWrapper ShouldMap<T>(this IWindsorContainer container) where T : class
{
List<GraphNode> graphNodes = new List<GraphNode>(container.Kernel.GraphNodes);
List<ComponentModel> componentModels = new List<ComponentModel>(Convert(graphNodes));

ComponentModel componentModel = componentModels.Find(x => x.Service == typeof(T));
if (componentModel == null)
throw new AssertionException(string.Format("Service {0} has not been registered in the container",
typeof (T).FullName));

return new ComponentWrapper {Container = container, Component = componentModel};
}

public static ComponentWrapper To<T>(this ComponentWrapper wrapper) where T : class
{
Type serviceType = wrapper.Component.Service;
try
{
var instance = wrapper.Container.Resolve(serviceType);

if (instance.GetType() != typeof(T))
throw new AssertionException(string.Format("Expected implementation of {0} to be {1}, but was {2}",
serviceType,
typeof(T).FullName, instance.GetType()));
}
catch(Exception exception)
{
throw new AssertionException(string.Format("An exception was thrown when resolving {0}. The exception message is: {1}",
serviceType, exception.Message), exception);
}

return wrapper;
}

public static ComponentWrapper WithLifeStyle(this ComponentWrapper wrapper, LifestyleType expectedExpectedLifestyle)
{
VerifyLifestyle(wrapper.Component.LifestyleType, expectedExpectedLifestyle);
return wrapper;
}

private static void VerifyLifestyle(LifestyleType actualLifestyle, LifestyleType expectedLifestyle)
{
if (actualLifestyle == LifestyleType.Undefined && expectedLifestyle == LifestyleType.Singleton)
return;

if(expectedLifestyle != actualLifestyle)
throw new AssertionException(string.Format("Expected lifestyle {0} but was {1}",
expectedLifestyle, actualLifestyle));
}

private static IEnumerable<ComponentModel> Convert(List<GraphNode> graphnodes)
{
foreach(GraphNode node in graphnodes)
yield return node as ComponentModel;
}
}

2 comments:

Gaz said...

That is a very nice idea. We will be stealing this code I think.

Øyvind Valland said...

Hi Gaz,

Glad this is of use to you. Please use the code freely, and please share the link to the post with others that might find it useful. If I expand significantly on this I'll post updates and maybe stick the source on BitBucket.

Ø