How to prepare the ASP.NET MVC Controllers to use Session and at the same time be testable, so in essence not use Session but rather use some Session abstraction? I am using Ninject so your examples could be based on that.
The problem is that the Session object is not available at all times in the controllers (like in ctor's) but I need to store something to the Session at application start (the global.asax.cs also does开发者_JAVA百科 not have access to the Session).
Just use a mock framework to create a mock HttpSessionStateBase and inject it into the controller context. With Rhino Mocks, this would be created using MockRepository.PartialMock<HttpSessionStateBase>()
(see below). The controller, during the test, will then operate on the mock Session.
var mockRepository = new MockRepository();
var controller = new MyController();
var mockHttpContext = mockRepository.PartialMock<HttpContextBase>();
var mockSessionState = mockRepository.PartialMock<HttpSessionStateBase>();
SetupResult.For(mockHttpContext.Session).Return(mockSessionState);
// Initialize other parts of the mock HTTP context, request context etc
controller.ControllerContext =
new ControllerContext(
new RequestContext(
mockHttpContext,
new RouteData()
),
controller
);
If you want your class to be testable, don't use non-testable (external) components there. When you try to mock them, you just work around the bad design. Re-design you controllers instead. A class shouldn't rely on external/global objects. That's one of the reasons why IoC is used.
You have two choices to separate implementation/infrastructure details from your controllers:
Minor abstraction
public interface ISession { string GetValue(string name); void SetValue(string name, string value); }
Domain abstraction.
public interface IStateData { bool IsPresent { get; } int MyDomainMeaningfulVariable { get; set; } }
In the latter case, the interface adds semantics over session - strongly typed, well named property. This is just as using NHibernate domain entities instead of sqlreader["DB_COLUMN_NAME"].
Then, of course, inject HTTP implementation of the interface (e.g. using HttpContext.Current) into controllers.
Model binders are also a good way to go, just as action filters are. They're not just for form data.
There are a couple of ways - either use a custom filter attribute to inject a session value into a controller action, or create a session object with an interface that can be mocked and inject it into the constructor of the controller.
Below is an example of the custom filter.
public class ProfileAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.ActionParameters["profileUsername"] = "some session value";
base.OnActionExecuting(filterContext);
}
}`
and a way to use it in a controller:
[ProfileAttribute]
public ActionResult Index(string profileUsername)
{
return View(profileUsername);
}
Which one you choose probably depends on how much you rely on session values, but either way is relatively testable.
You can't store anything to Session at application start. Session = client initiated interaction. You don't have Sessions for all clients at application start.
Controller usually do not interact with session directly - it makes controllerd dependent on session. Instead controller methods (actions) accepts parameters which are automatically filled from the session by creating custom ModelBinder. Simple example:
public class MyDataModelBinder : IModelBinder
{
private const string _key = "MyData";
public object BindModel(ControllerContext context, ModelBindingContext bindingContext)
{
MyData data = (MyData)context.HttpContext.Session[_key];
if (data == null)
{
data = new MyData();
context.HttpContext.Session[_key] = data;
}
return data;
}
}
You will than register your binder in Application_Start (Global.asax):
ModelBinders.Binders.Add(typeof(Mydata), new MyDataModelBinder());
And you define your action like:
public ActionResult MyAction(MyData data)
{ ... }
As you can see the controller is in no way dependent on Session and it is fully testable.
精彩评论