There are many
good articles published on how to write a
custom Filter Provider in MVC 3 in order to make dependency injection possible on the web. So I'm not going to talk about that, but there doesn't seem to be many articles on how to test those custom filter providers.
The role of the filter provider is to simply return filters for a given controller, but this post assumes you know what a filter provider does so I'm going to skip the explaination. I've provided an MSDN link to the FilterProvider technical page for more information.
The
FilterAttributeFilterProvider as provided by the ASP.NET MVC framework looks like the following: (I have the ASP.NET MVC source code on my machine, will show how to install and access this code in another post...)
public class FilterAttributeFilterProvider : IFilterProvider
{
private readonly bool _cacheAttributeInstances;
public FilterAttributeFilterProvider()
: this(true) {
}
public FilterAttributeFilterProvider(bool cacheAttributeInstances) {
_cacheAttributeInstances = cacheAttributeInstances;
}
protected virtual IEnumerable<filterattribute> GetActionAttributes(ControllerContext
controllerContext, ActionDescriptor actionDescriptor) {
return actionDescriptor.GetFilterAttributes(_cacheAttributeInstances);
}
protected virtual IEnumerable<filterattribute> GetControllerAttributes(ControllerContext
controllerContext, ActionDescriptor actionDescriptor) {
return actionDescriptor.ControllerDescriptor.GetFilterAttributes(_cacheAttributeInstances);
}
public virtual IEnumerable<filter> GetFilters(ControllerContext controllerContext,
ActionDescriptor actionDescriptor) {
ControllerBase controller = controllerContext.Controller;
if (controller == null) {
return Enumerable.Empty<filter>();
}
var typeFilters = GetControllerAttributes(controllerContext, actionDescriptor)
.Select(attr => new Filter(attr, FilterScope.Controller, null));
var methodFilters = GetActionAttributes(controllerContext, actionDescriptor)
.Select(attr => new Filter(attr, FilterScope.Action, null));
return typeFilters.Concat(methodFilters).ToList();
}
}
It is the
GetFilters method we are interested in as this is the method we override in our custom filter provider to provide things like dependency injection support.
So your custom filter provider might look something like the following (IoC container StructureMap specific):
public class StructureMapFilterProvider : FilterAttributeFilterProvider
{
private readonly IContainer container;
public StructureMapFilterProvider(IContainer container)
{
container = container;
}
public override IEnumerable<Filter> GetFilters(ControllerContext controllerContext,
ActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(controllerContext, actionDescriptor);
if (filters != null)
{
foreach (var filter in filters)
{
container.BuildUp(filter.Instance);
}
return filters;
}
return default(IEnumerable<Filter>);
}
}
In the above filter provider, we are getting a list of filters for the passed controller context, and then calling
BuildUp on those instances using StructureMap IoC container. This is a neat feature of StructureMap that allows you to support property injection without having to use decorators in the filter classes. If you'd like to learn more about StructureMaps
BuildUp feature, please see this link:
http://codebetter.com/jeremymiller/2009/01/16/quot-buildup-quot-existing-objects-with-structuremap/
You probably know already that ideally you'd want to call the
base.GetFilters(ControllerContext, ActionDescriptor) to be on an interface so it can be easilly stubbed out in out unit tests, but it's not, its part of the superclass that we are deriving from.
So we are going to have to construct a
ControllerContext and
ActionDescriptor in order to pass into our filter provider during testing. All we are really aiming to test here is that the container
BuildUp method is called against the filter instance. Your requirement might be slightly different depending on what you are trying to achieve and perhaps the IoC container you might or might not be using. But the principles should be the same.
Before I show how to test the above we need some supporting code. The first being a mocked controller that is decorated with a filter:
public class FilterProviderControllerMock : Controller
{
[CustomErrorHandler]
public void MockAction()
{
}
}
So here we simply have a controller with an action named
MockAction that includes a custom action and does nothing. Notice how the action is decorated with a
CustomErorHandler attribute - which is a custom filter. It's this attribute that we want to inject our dependencies into. I've not shown the code for this as it's not important, what is important is to realise that this filter has dependencies that cannot be injected via the constructor due to limitations in .NET (i.e. it being an attribute).
First consider the actual test code (note: I'm using
Moq as my mocking tool of choice - just because it's a change to
Rhino):
[TestFixture]
public class StructureMapFilterProviderTests
{
private Mock<IContainer> containerMock;
private StructureMapFilterProvider filterProvider;
private Filter customFilter;
private List<Filter> customFilterCollection;
private CustomErrorHandlerAttribute customActionFilter;
private Mock<HttpContextBase> httpContextMock;
[TestFixtureSetUp]
public void Setup()
{
customActionFilter = new CustomErrorHandlerAttribute();
customFilter = new Filter(customActionFilter, FilterScope.Action, 1);
customFilterCollection = new List<filter>();
customFilterCollection.Add(customFilter);
containerMock = new Mock();
filterProvider = new StructureMapFilterProvider(this.containerMock.Object);
httpContextMock = new Mock<HttpContextBase>();
}
[Test]
public void CanInterceptCreationOfFilters()
{
// Arrange
containerMock.Setup(x => x.BuildUp(customActionFilter));
var controllerMock = new FilterProviderControllerMock();
var routeData = new RouteData();
var controllerContext = new ControllerContext(this.httpContextMock.Object, routeData, controllerMock);
ControllerDescriptor controllerDescriptor = new
ReflectedControllerDescriptor(typeof(FilterProviderControllerMock));
var mockActionMethodInfo = controllerMock.GetType()
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.Name.Equals("MockAction")).FirstOrDefault();
ActionDescriptor actionDescriptor = new
ReflectedActionDescriptor(mockActionMethodInfo, "MockAction", controllerDescriptor);
// Act
var filters = filterProvider.GetFilters(controllerContext, actionDescriptor);
// Assert
containerMock.Verify(x => x.BuildUp(this.customActionFilter), Times.Once());
}
}
So what's going on here, there seems to be a lot of code. There is because the superclass filter provider is hard to test due to how it was designed (using inheritance).
So the test above is simply creating a
ControllerContext and an
ActionDescriptor which is required in order to call the filter provider. Doing these things requires a bit of .NET reflection that would normally be done by the MVC framework at runtime.
Once reflection has been used and the correct action method has been identified, the provider can easily select the correct filter then our custom filter can build it up (inject all required dependencies into the filter). We then finally assert that the build up happens on our custom filter at least once otherwise the test will fail.
I'm sure in later releases of ASP.NET MVC this will improve, but for now this is a work around to test your custom filter providers.
This sample code is up on BitBucket here if you want it:
http://code.simonrhart.com/mvc-3-examples/src/5b122b445c86/Testing%20StructureMap%20Filter%20Provider
Enjoy!