Unit Testing ASP.NET MVC ModelBinders
For today's post, we focus on unit testing our ModelBinders from our paging example.
Recently, a reader posted a comment requesting me to add a unit test for testing our Model Binder with QueryStrings.
The post they were referring to was the ASP.NET MVC ModelBinder: Use ModelBinders for QueryStrings.
Today, as a quick post, we'll make a unit test for our ModelBinder.
Mockin' it up!
For my unit tests, I use Rhino Mocks. I know some people like Moq or TypeMock better, but this is a preference of mine.
First, we start with a NameValueCollection of "queryStrings."
// Arrange var queryStringCollection = new NameValueCollection { {"Page", "1"}, {"Size", "20"} };
Next, we need to create our ModelBindingContext. This requires a NameValueCollectionValueProvider (wow...that's a mouthful) and a ModelMetaDataProvider.
The ValueProvider is easy. We simply pass in the queryStringCollection whereas the ModelMetadata needs the model type that we'll be using for our ModelBinder which is the PagingModel.
var valueProvider = new NameValueCollectionValueProvider(queryStringCollection, null); var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(PagingModel)); var bindingContext = new ModelBindingContext { ModelName = "PagingModel", ValueProvider = valueProvider, ModelMetadata = modelMetadata };
As you can see, we finally create the ModelBindingContext with all of our properties ready for use.
Mocking HttpContext
Now the fun part.
Most developers hate mocking up the HttpContext. Using RhinoMocks, I have no qualms or issues about mocking it up.
I just need 2 properties from the HttpContext: Request and QueryString.
var mockHttpContext = MockRepository.GenerateMock<HttpContextBase>(); var mockRequest = MockRepository.GenerateMock<HttpRequestBase>(); mockRequest.Stub(e => e.QueryString).Return(queryStringCollection); mockHttpContext.Stub(x => x.Request).Return(mockRequest);
Simple, right?
Controlling the Controller
Next, we need a controller to pass into the BindModel. So let's create our FAQ controller and instantiate our PagingBinder ModelBinder.
var controller = new FaqController(); controller.ControllerContext = new ControllerContext( mockHttpContext, new RouteData(), controller); var binder = new PagingBinder();
For the grand finale, we call the BindModel with the ControllerContext and BindingContext passed into method.
// Act
PagingModel result = (PagingModel)binder.BindModel(controller.ControllerContext, bindingContext);
Assert the Assert
In our Assert, we simply check the PageSize and PageIndex is 20 and 1, respectively.
Here is our complete unit test for our PagingBinder ModelBinder.
[TestMethod] public void RetrieveThePagingDataFromQueryStringUsingModelBinder() { // Arrange var queryStringCollection = new NameValueCollection { {"Page", "1"}, {"Size", "20"} }; var valueProvider = new NameValueCollectionValueProvider(queryStringCollection, null); var modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(PagingModel)); var bindingContext = new ModelBindingContext { ModelName = "PagingModel", ValueProvider = valueProvider, ModelMetadata = modelMetadata }; var mockHttpContext = MockRepository.GenerateMock<HttpContextBase>(); var mockRequest = MockRepository.GenerateMock<HttpRequestBase>(); mockRequest.Stub(e => e.QueryString).Return(queryStringCollection); mockHttpContext.Stub(x => x.Request).Return(mockRequest); var controller = new FaqController(); controller.ControllerContext = new ControllerContext( mockHttpContext, new RouteData(), controller); var binder = new PagingBinder(); // Act PagingModel result = (PagingModel)binder.BindModel(controller.ControllerContext, bindingContext); // Assert Assert.AreEqual(result.PageSize, 20); Assert.AreEqual(result.PageIndex, 1); }
Conclusion
The unit test for model binders almost have the same pattern for each specific model binder. If you wanted to use Request.Form, you could easily add in the mockRequest.Forms and add your own data into a NameValueCollection as well.
Mocking up the HttpContext is not as hard as it was before. Thanks to RhinoMocks for that simple interface.
Also, I want to thank Janek for the comment.