Wednesday, April 22, 2009

ASP.NET MVC and Testing FilterActions

I’m preparing my website, which benefits NHibernate and ASP.NET MVC so I finally got a chance to actually do something with this nice pair. Since the pattern of SessionPerRequest and TransactionPerRequest is useful, I intended to automagically create a new session upon activating my Controller’s action, where necessary, since this will make your session management code separate from your controller’s code and you no longer need to worry about it. This part is very easy to do thanks to ActionFilterAttributes in ASP.NET MVC:

public class TransactionPerRequest : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
NHibernateSessionHolder.Current.BeginTransaction();
}

public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Exception == null)
{
Commit();
}
else
{
Rollback();
}
}

protected virtual void Commit()
{
if (NHibernateSessionHolder.Current.Transaction.IsActive)
{
NHibernateSessionHolder.Current.Transaction.Commit();
}
}

protected virtual void Rollback()
{
if (NHibernateSessionHolder.Current.Transaction.IsActive)
{
NHibernateSessionHolder.Current.Transaction.Rollback();
}
}
}

NHibernateSessionHolder is actually a static class and is initialized in Global.asax when application is started. Is stores the session in current HttpContext object.

So, how do we test this, you might ask? This leads to another question that how do you generally test ActionFilterAttributes? There are two ways to achieve this.

If you can test the filter attribute’s behavior in your controller, the easiest way would be to directly call the action on the controller. You need to create the route values and use the ControllerActionInvoker to call the action of your controller. Also mocking HttpContext is preferred for obvious reasons. My test case would look like this:

[TestMethod]
public void Transaction_Is_Automatically_Opened_Using_Transaction_Per_Request()
{
var httpContext = new Mock<HttpContextBase>().Object;
var controller = new AccountController();
var controllerContext = new ControllerContext(httpContext, controller.GetCreateActionRouteData(), controller);
var controllerInvoker = new ControllerActionInvoker();

controllerInvoker.InvokeAction(controllerContext, "Create");

Assert.IsTrue(controller.TransactinWasCreated);
}
But sometimes, you need to test the internal behavior of your filter attribute and it would not be possible doing so in your Controller classes. Let’s say, you want to check if the OnResultExecuted method handles exception properly by rolling back the transaction. To do it this way, you need to create or mock ActionExecutingContext and ResultExecutedContext:
[TestMethod]
public void Transaction_Is_Automatically_Rolled_Back_If_Exception_Thrown()
{
var httpContext = new Mock<HttpContextBase>().Object;
var actionDescriptor = new Mock<ActionDescriptor>().Object;
var actionResult = new Mock<ActionResult>().Object;

var controller = new AccountController();
var controllerContext = new ControllerContext(httpContext, new RouteData(), controller);
var filterContext = new ActionExecutingContext(controllerContext, actionDescriptor, new RouteValueDictionary());
var resultContext = new ResultExecutedContext(controllerContext, actionResult, false, new Exception("an exception is thrown"));
var transactionAttrib = new TestableTransactionAttribute();

transactionAttrib.OnActionExecuting(filterContext);
transactionAttrib.OnResultExecuted(resultContext);

Assert.IsFalse(NHibernateSessionHolder.Current.Transaction.IsActive);
Assert.IsFalse(transactionAttrib.IsCommitted);
Assert.IsTrue(transactionAttrib.IsRolledback);
}

Submit this story to DotNetKicks Shout it

No comments: