So far it looks like the dominant pattern in unit testing MVC Controllers is via a Test Specific Subclass and for some that's a pain. Especially when you're going to have a lot of controllers, that's a lot of the same code you'll be repeating over and over. So in the principle of DRY let's look at using Castle Project's Dynamic Proxy to do away with these controller subclasses.
The main principle behind this is creating a simple dynamic proxy
and attaching an interceptor to it. We'll then intercept calls to
RenderView and RedirectToAction and then present our captured results
back to the user. The first step is creating our proxy which is simply
a called to ProxyGenerator.CreateClassProxy, this is also where we
attach our method interceptor. We pass constructor arguments as well to
provide support for people who want to use dependency injection with
their controllers.
public class ControllerTester<T> where T : Controller
{
private static ProxyGenerator generator = new ProxyGenerator();
private T controller;
private MethodInterceptor interceptor;
public ControllerTester(params object[] args)
{
this.interceptor = new MethodInterceptor();
this.controller = generator.CreateClassProxy(typeof(T), new IInterceptor[] { interceptor }, args) as T;
}
...
}
The
only other interesting piece of code here is intercepting the
appropriate methods "RenderView" and "RedirectToAction" and storing
their results.
public void Intercept(IInvocation invocation)
{
if(invocation.Method.Name == "RenderView")
{
renderArgs = new RenderArgs(invocation.Arguments[0].ToString(), invocation.Arguments[1].ToString(), invocation.Arguments[2]);
return;
}
else if(invocation.Method.Name == "RedirectToAction")
{
redirectArgs = new RedirectArgs(invocation.Arguments[0]);
return;
}
invocation.Proceed();
}
So for a controller action as follows.
[ControllerAction]
public void ComplexRedirectAction()
{
RedirectToAction(new { Controller = "cont", Action = "act", Pirates = "Yarr" });
}
We can test verifty it redirects to the approriate action without needing a test specific subclass.
[Test]
public void RecordsComplexRedirectData()
{
ControllerTester<StubController> controllerTester = new ControllerTester<StubController>();
controllerTester.Controller.ComplexRedirectAction();
Assert.AreEqual("act", controllerTester.RedirectArgs.Action);
Assert.AreEqual("cont", controllerTester.RedirectArgs.Controller);
Assert.AreEqual("Yarr", controllerTester.RedirectArgs.Values["Pirates"]);
}
However Scott Gu has mentioned that they'll be making it easier to test controllers in the next version of the mvc framework. But this is still a decent pattern if you're constantly needing a Test Specific Subclass to override some methods.


0 Comments