Saturday, February 14, 2009

Testing the Http Session in ASP.NET MVC Framework

Update: You can actually use the MockSession provided in MVCContrib on Codeplex, since it does everything that this article describes and more.


I've been working with Microsoft ASP.NET for over 5 years now, and frankly nothing has impressed me more than the new ASP.NET MVC Framework that is now in Release Candidate.

One of the main advantages of the framework is testability, however one problem I ran into early on is how to test the Http Session. The only way I found on the web to test the session was to mock out HttpSessionStateBase and that just looked ugly to me.

In my past experience in ASP.NET I've always injected my own Session object to a Presenter class, which made it easier to test without a whole lot of ceremony (other than the Model-View-Presenter pattern itself).

My solution was to create an abstract class that extends System.Web.Mvc.Controller which allows for a testSession to be set from unit tests. Meanwhile the real HttpSession is returned only when the testSession is null (i.e when running in a web server)

You'll need to extend SessionableController if that particular controller wants to use the session, and create a parameterless contructor.

To test these SessionableController's you'll need to create a new SessionStub object and pass it into the SessionController.



public abstract class SessionableController : Controller
{
//only used when testing, otherwise it's null
private readonly HttpSessionStateBase testSession;

//used by ASP.NET
protected SessionableController()
{
}

//used for testing
protected SessionableController(HttpSessionStateBase state)
{
testSession = state;
}

public new HttpSessionStateBase Session
{
get { return testSession ?? GetRealHttpSession(); }
}

private HttpSessionStateBase GetRealHttpSession()
{
return
new HttpSessionStateWrapper(
(HttpSessionState)
HttpContext.Items["AspSession"]);
}
}

public class ShoppingCartController : SessionableController
{
private ShoppingCart cart;

//used by ASP.NET
public ShoppingCartController()
{
}
//used by unit tests
public ShoppingCartController(HttpSessionStateBase session) : base(session){}

//
// GET: /ShoppingCart/

public ActionResult Index()
{
if (Session["cart"] == null)
Session["cart"] = cart = new ShoppingCart();
cart = (ShoppingCart) Session["cart"];

if (cart.NumberOfItems() == 0)
cart.AddItem(new ShoppingCartItem("shirt", 10.99M));

return View(cart);
}


[TestFixture]
public class ShoppingCartTest
{
[Test]
public void Should_Be_Able_To_Use_A_Stub_Session_()
{
new ShoppingCartController(new SessionStub()).Index();
}
}

//Pretends to be an ASP.NET Session, but really is just a list.
//Helps testing controllers without a web container.
internal class SessionStub: HttpSessionStateBase
{
private readonly Dictionary<string, object> sessionContents =
new Dictionary<string, object>();

public override object this[string name]
{
get
{
return sessionContents.ContainsKey(name)
? sessionContents[name] : null;
}
set { sessionContents[name] = value; }
}
}