Introduction
While talking to my friend regarding his project, he told me about how he is doing unit testing which involves sessions in ASP.NET MVC3. His team is actually using a “HttpSimulator” which simulates the web request and then the do unit test by verifing the session by interacting with the simulator. When digging further I have come to know that this way of unit testing session objects are influenced from the “ASP.NET Webforms”. It reminds me the talk of Neil Ford on Function Programming. In that video he talks about an analogy called “Axe and Chain Saw” to explain our way of thinking as
“When we give a chain saw to people who were cutting trees by axe, they would tend to use chain saw in the same way as the use Axe. Which is obviously inefficient. So we should understand at the capabilities of the tool in our hand before we using it”.
ASP.NET MVC3 is far better than Web Forms when it comes to unit testing. We don’t need to use a simulator to test against our sessions. There is a better way to do this MVC3 and in this blog post we are going to explore it.
Time for Code
Shopping Cart is the first thing that strikes our mind when we want to quote an example for using Http session. So, I am going to show an app called “MyShop” a mini shopping site through which I am going to explain the concepts involved. The application flow would be as follows
Models
The models are simple, straight forward and self explanatory.
I’ve tried my level best to keep the model as simple as possible. So, Cart in MyShop will have only two public methods. One to add a product to the Cart’s Line and another one to retrieve all the products inside the Cart’s line.
The CartController Version 1.0
In this CartController version 1.0 we have two public methods Index and AddToCart which are dependent on HttpSession object. This dependency inside the methods is actually preventing us from unit testing the CartController in simple way and we have no choice other than implementing a “Http Simulator” to unit test these two methods. As I said before there is better to do is! Here we go!!
The CartController Version 2.0
No more Sessions!!.. Yeah.. We have got rid of the dependency on the session object by adding a new parameter called cart. Now you can use easily unit test the CartController as follows
Okay we made it easy for unit testing by moving the dependency out of the method and introduced the cart as the parameter. But how does my MVC3 framework will know the cart parameter should come from session object ?… Good Catch!! and here comes the magic called custom ModelBinder
ModelBinder – A brief background
Model binding is an exciting feature in MVC3 framework which automatically creates the C# objects directly from Http request and pass it to the Action methods in controller as parameter values. It uses a default model binder which looks at the form values, query string values that are submitted with the Http Request and create the model object.
CartModelBinder
In our case, we need to have a object of Cart which is populated from the Session object and not from the HttpRequest. The default model binder used by MVC3 has no idea about session object. So, Its our responsibility to tell to the MVC3 framework
“Hey! If there is any parameter of type Cart in controller action method, then use my own custom model binder called CartModelBinder to create the object”
There are two steps to do the above said operation
1. Creating the custom model binder by inheriting the IModelBinder interface
2. Registering our custom model binder in the Global.asax.cs file
That’s all.. MVC3 takes care of rest
Summary
In this blog post we have explored how we can get rid of “Http Simulator” to unit test the controllers which involves Session objects using custom model binder. You can download the working example of “MyShop” showcased in this blog post from here. Refer my next blog post to check out how to do unit testing with the custom model binder itself.
While talking to my friend regarding his project, he told me about how he is doing unit testing which involves sessions in ASP.NET MVC3. His team is actually using a “HttpSimulator” which simulates the web request and then the do unit test by verifing the session by interacting with the simulator. When digging further I have come to know that this way of unit testing session objects are influenced from the “ASP.NET Webforms”. It reminds me the talk of Neil Ford on Function Programming. In that video he talks about an analogy called “Axe and Chain Saw” to explain our way of thinking as
“When we give a chain saw to people who were cutting trees by axe, they would tend to use chain saw in the same way as the use Axe. Which is obviously inefficient. So we should understand at the capabilities of the tool in our hand before we using it”.
ASP.NET MVC3 is far better than Web Forms when it comes to unit testing. We don’t need to use a simulator to test against our sessions. There is a better way to do this MVC3 and in this blog post we are going to explore it.
Time for Code
Shopping Cart is the first thing that strikes our mind when we want to quote an example for using Http session. So, I am going to show an app called “MyShop” a mini shopping site through which I am going to explain the concepts involved. The application flow would be as follows
Models
The models are simple, straight forward and self explanatory.
I’ve tried my level best to keep the model as simple as possible. So, Cart in MyShop will have only two public methods. One to add a product to the Cart’s Line and another one to retrieve all the products inside the Cart’s line.
The CartController Version 1.0
In this CartController version 1.0 we have two public methods Index and AddToCart which are dependent on HttpSession object. This dependency inside the methods is actually preventing us from unit testing the CartController in simple way and we have no choice other than implementing a “Http Simulator” to unit test these two methods. As I said before there is better to do is! Here we go!!
The CartController Version 2.0
No more Sessions!!.. Yeah.. We have got rid of the dependency on the session object by adding a new parameter called cart. Now you can use easily unit test the CartController as follows
Okay we made it easy for unit testing by moving the dependency out of the method and introduced the cart as the parameter. But how does my MVC3 framework will know the cart parameter should come from session object ?… Good Catch!! and here comes the magic called custom ModelBinder
ModelBinder – A brief background
Model binding is an exciting feature in MVC3 framework which automatically creates the C# objects directly from Http request and pass it to the Action methods in controller as parameter values. It uses a default model binder which looks at the form values, query string values that are submitted with the Http Request and create the model object.
CartModelBinder
In our case, we need to have a object of Cart which is populated from the Session object and not from the HttpRequest. The default model binder used by MVC3 has no idea about session object. So, Its our responsibility to tell to the MVC3 framework
“Hey! If there is any parameter of type Cart in controller action method, then use my own custom model binder called CartModelBinder to create the object”
There are two steps to do the above said operation
1. Creating the custom model binder by inheriting the IModelBinder interface
2. Registering our custom model binder in the Global.asax.cs file
That’s all.. MVC3 takes care of rest
Summary
In this blog post we have explored how we can get rid of “Http Simulator” to unit test the controllers which involves Session objects using custom model binder. You can download the working example of “MyShop” showcased in this blog post from here. Refer my next blog post to check out how to do unit testing with the custom model binder itself.
If you want to test your custom binder, perhaps Ivonna (http://ivonna.biz/) can help you. No more HttpSimulators!
ReplyDeletecould you not uses a small wrapper around session. Something like ISessionProvider. Then you could simply replace your calls to http session with a mocked session provider during unit testing.
ReplyDeletenew CartController(new DictionarySessionProvider)
Another alternative is to subclass the controller and override GetCartFromSession .. not as nice but it will work.
Thanks sboulay for pointing out an alternative option.
DeleteA downside of using mocking is, it adds more verbosity to the unit tests and it may affect the readability of the unit tests.
On the other hand Model Binder hides the source of the model from the action methods which results in cleaner and more readable unit test.
Also, we can still use dependency injection in combination with Model Binder which would result a more robust solution as follows
public class CartModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var sessionProvider = ObjectFactory.GetInstance();
return sessionProvider.GetCart();
}
}
One limitation I have found in using Custom Binder for session object is we are adding an extra parameter to the action method which may break other tests.
So depending on the context, we may have to pick the best one which suits the requirement in hand.
seems similar to steve sanderson's sort of implementation of cart model binder and the rest or i am missing something.
ReplyDeleteVery good solution but I am struggling to get it working without passing a parameter.
ReplyDeleteFor example: I am adding the product to cart as bellow:
[HttpPost]
public ActionResult AddToCartfromAbout(Cart cart, int productId = 2)
{
var product = _productRepository.Products.First(p => p.ProductId == productId);
cart.AddItem(product, 1);
return View("About");
}
About Action does not accept any parameter. So how I will get the Cart value there without using Session["Cart"] directly. About Action is as bellow.
public ActionResult About()
{
// Need something here to get the value of cart
return View(cart);
}
For easy solution to test session variables used in controller method, please go through the below link. It is worked well for me and even without any major code changes on my existing logic.
ReplyDeletehttp://www.codeproject.com/Articles/240227/MVC-Test-Driven-Developement-Session-Variables
This comment has been removed by the author.
ReplyDeleteNice way of using model binders. but everything in asp.net mvc is mockable unlike webforms. Sessions, HttpContext, ControllerContext. http://stephenwalther.com/archive/2008/07/01/asp-net-mvc-tip-12-faking-the-controller-context
ReplyDeleteFind the list of Top 10 Forex Broker Canada Based On Independent Ratings, Reviews, And Online Presence. Find Out Which Broker You Should Choose.
ReplyDelete