Introduction
When you write a controller which is unit testable, it would be better to avoid any dependency to the HttpContext
to make things easier. If you are using any of the session variables in your controller, it will make your controller difficult to test. Here, I am trying to provide a solution for this which may help you in this scenario.
Using the Code
Step 1: Write an interface, IStateProvider
:
public interface IStateProvider
{
object this[string key] { get; set; }
void Remove(string key);
}
Step 2: Write a class, SessionStateProvider
implementing the IStateProvider
. The SessionStateProvider
will make use of the Session
to store the variables.
public class SessionStateProvider : IStateProvider
{
public object this[string key]{
get
{
return HttpContext.Current.Session[key];
}
set
{
HttpContext.Current.Session[key] = value;
}
}
public void Remove(string key)
{
HttpContext.Current.Session.Remove(key);
}
}
Step 3: Write a base class for all of your controllers, say BaseController
and expose one property say, CSession
in it. You can see that this property is not set initially.
public class BaseController : Controller
{
public IStateProvider CSession
{
get;
set;
}
}
Step 4: Write a custom controller factory, where we will set the State Provider as the session state provider. You can also inject here other dependencies, if any.
public class MyControllerFactory:DefaultControllerFactory
{
public override IController CreateController
(System.Web.Routing.RequestContext requestContext, string controllerName)
{
IController controller;
controller = base.CreateController(requestContext, controllerName) ;
if (controller is BaseController)
{
var baseController = (BaseController)controller;
baseController.CSession = new SessionStateProvider();
}
return controller;
}
}
Step 5: Register your controller Factory
in your Global.asax.cs file.
void Application_Start(object sender, EventArgs e)
{
ControllerBuilder.Current.SetControllerFactory
(new Fadt.CMS.Web.Core.CMSControllerFactory());
RegisterRoutes(RouteTable.Routes);
}
Step 6: Extend all your controllers from the BaseController
. Now instead of using Session
, you can use CSession
. Since the Controller factory
has injected the SessionStateProvider
to the CSession
property, in turn all the values you set to CSession
will get stored in Session
.
public class MyController : BaseController
{
public MyController()
{
CSession["MY_VAR"] = obj;
CSession.Remove(“MY_VAR”);
}
Testing the Controller
In the test project, write another implementation of IStateProvider
. This implementation will save the variables in a dictionary
rather than in Session
. So by injecting the dependency through the Controller
, we have decoupled the controller from the HttpContext
.
public class DictionaryStateProvider:IStateProvider
{
private Dictionary<string,object> stateValueCollection =
new Dictionary<string,object>();
public object this[string key]
{
get
{
if (stateValueCollection.ContainsKey(key))
return stateValueCollection[key];
else
return null;
}
set
{
stateValueCollection[key] = value;
}
}
public void Remove(string key)
{
stateValueCollection.Remove(key);
}
}
Now in the test method, write an initialization method to initialize the controller for testing.
public static void InitializeController(BaseController controller)
{
controller.CSession = new DictionaryStateProvider();
}
[TestMethod]
public void Is_Index_Data_Loaded_Properly()
{
MyController controller = new MyController();
InitializeController(controller);
controller.Index() Assert.IsTrue(true);
}
Points of Interest
Here, we have avoided the dependency of the controllers to the HttpContext
which will ease the test driven development. The whole idea is to enable the developer to develop and test the controller independent of the other dependencies.
Hope that you guys will find this helpful... please keep posting your comments. :-)