Issues mocking the MVC ControllerContext request
-
07-07-2019 - |
Question
I'm pretty new to testing and mocking and i am trying to write a test that will ensure that my validation logic is setting ModelState errors correctly.
What I'm seeing is that the controller.ControllerContext.HttpContext.Request is set the first time I check it but every time after that the Request is null.
This is causing a null reference exception in the PopulateDictionary method of the *ValueProviderDictionary * class in the MVC source because the request object is accessed multiple times in that method without ensuring the request is not null.
I'm gluing together several techniques and helpers that I have found while researching how to overcome some of the issues I have run into thus far so at this point I'm a little unsure where I may have introduced the issue.
Am I using mock objects incorrectly here?
Failing Test
//Test
public void Test_FooController_OnActionExecuting_ShouldMapStateToAFooModel()
{
//Arrange
DataAccessFactoryMocks.MockAllDaos();
var controller = new FooController();
var testFormCollection = new NameValueCollection();
testFormCollection.Add("foo.CustomerID", "3");
testFormCollection.Add("_fooForm", SerializationUtils.Serialize(new FooModel()));
var mockHttpContext = new MockHttpContext(controller, "POST", testFormCollection, null);
//Accessor used to run the protected OnActionExecuting method in my controller
var accessor = new FooControllerAccessor(controller);
//Request is set, assertion passes
Assert.IsNotNull(controller.ControllerContext.HttpContext.Request.Form);
//Request is null when accessing the property a second time, assertion fails
Assert.IsNotNull(controller.ControllerContext.HttpContext.Request.QueryString);
//Act
accessor.OnActionExecuting(new ActionExecutingContext(controller.ControllerContext, MockRepository.GenerateStub<ActionDescriptor>(), new Dictionary<string, object>()));
//Assert
Assert.That(controller.ModelState.IsValid == false);
}
Test Helper
//Test helper to create httpcontext and set controller context accordingly
public class MockHttpContext
{
public HttpContextBase HttpContext { get; private set; }
public HttpRequestBase Request { get; private set; }
public HttpResponseBase Response { get; private set; }
public RouteData RouteData { get; private set; }
public MockHttpContext(Controller onController)
{
//Setup the common context components and their relationships
HttpContext = MockRepository.GenerateMock<HttpContextBase>();
Request = MockRepository.GenerateMock<HttpRequestBase>();
Response = MockRepository.GenerateMock<HttpResponseBase>();
//Setup the context, request, response relationship
HttpContext.Stub(c => c.Request).Return(Request);
HttpContext.Stub(c => c.Response).Return(Response);
Request.Stub(r => r.Cookies).Return(new HttpCookieCollection());
Response.Stub(r => r.Cookies).Return(new HttpCookieCollection());
Request.Stub(r => r.QueryString).Return(new NameValueCollection());
Request.Stub(r => r.Form).Return(new NameValueCollection());
//Apply the context to the suppplied controller
var rc = new RequestContext(HttpContext, new RouteData());
onController.ControllerContext = new ControllerContext(rc, onController);
}
public MockHttpContext(Controller onController, string httpRequestType, NameValueCollection form, NameValueCollection querystring)
{
//Setup the common context components and their relationships
HttpContext = MockRepository.GenerateMock<HttpContextBase>();
Request = MockRepository.GenerateMock<HttpRequestBase>();
Response = MockRepository.GenerateMock<HttpResponseBase>();
//Setup request type based on parameter value
Request.Stub(r => r.RequestType).Return(httpRequestType);
//Setup the context, request, response relationship
HttpContext.Stub(c => c.Request).Return(Request);
HttpContext.Stub(c => c.Response).Return(Response);
Request.Stub(r => r.Cookies).Return(new HttpCookieCollection());
Response.Stub(r => r.Cookies).Return(new HttpCookieCollection());
Request.Stub(r => r.QueryString).Return(querystring);
Request.Stub(r => r.Form).Return(form);
//Apply the context to the suppplied controller
var rc = new RequestContext(HttpContext, new RouteData());
onController.ControllerContext = new ControllerContext(rc, onController);
}
}
Working Test using MvcContrib.TestHelper
public void Test_FooController_OnActionExecuting_ShouldMapStateToAFooModel()
{
//Arrange
DataAccessFactoryMocks.MockAllDaos();
TestControllerBuilder builder = new TestControllerBuilder();
builder.Form.Add("fooModel.CustomerID", "3");
builder.HttpContext.Request.Stub(r => r.RequestType).Return("POST");
FooController controller = builder.CreateController<FooController>();
var accessor = new FooControllerAccessor(controller);
//Act
accessor.OnActionExecuting(new ActionExecutingContext(controller.ControllerContext, MockRepository.GenerateStub<ActionDescriptor>(), new Dictionary<string, object>()));
//Assert
Assert.IsFalse(controller.ModelState.IsValid);
}
Solution
I would suggest you using the excellent MVCContrib TestHelper for unit testing your ASP.NET MVC controllers with Rhino Mocks. You will see a drastic simplification of your unit tests and increased readability.
OTHER TIPS
What I understand from your question is that mocking of ControllerContext could also be replaced by a stub object because the goal is not testing ControllerContext behavior. Also, I am no really sure why you need a FooControllerAccessor while your only concern is to asserts the ModelState, so I left it out in here:
public void Test_FooController_OnActionExecuting_ShouldMapStateToAFooModel()
{
// Arrange
var action = new FooController()
.Action("index")
.RequestData(new Dictionary<string, object>()
{
{"foo.CustomerID", 3},
{"_fooForm", new FooModel()}
});
//Act
var modelState = action.ValidateRequest();
//Assert
Assert.That(modelState.IsValid == false);
}
To use this code you should install Xania.AspNet.Simulator (at time of writing v1.4.0-beta5) works for Mvc4 and Mvc5
PM > install-package Xania.AspNet.Simulator -Pre
For more examples check out the following: