Wednesday, May 30, 2012

Moq: Mocking HttpContext in your MVC3 unit tests

Lets take the following action method on our Home controller.

public ActionResult TestAction()
{
    var idFromCookie = Request.Cookies["ID"].Value;
    var model = new TestActionViewModel() { Id = idFromCookie };
    return View("TestAction", model);
}

Unit testing this action method can be difficult due to its dependency on the Request object.  Luckily the Request object here maps to the HttpContext of the controller.  The HttpContext on the controller is an instance of HttpContextBase and can be mocked in unit tests.

Below is a class I currently use to mock out the HttpContext.  Note that I have many other properties mocked that are part of the HttpContext.  I plan on doing other blog posts using those different properties in the future.

using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Moq;

public class MockContext
{
    public Mock<RequestContext> RoutingRequestContext { get; private set; }
    public Mock<HttpContextBase> Http { get; private set; }
    public Mock<HttpServerUtilityBase> Server { get; private set; }
    public Mock<HttpResponseBase> Response { get; private set; }
    public Mock<HttpRequestBase> Request { get; private set; }
    public Mock<HttpSessionStateBase> Session { get; private set; }
    public Mock<ActionExecutingContext> ActionExecuting { get; private set; }
    public HttpCookieCollection Cookies { get; private set; }

    public MockContext()
    {
        this.RoutingRequestContext = new Mock<RequestContext>(MockBehavior.Loose);
        this.ActionExecuting = new Mock<ActionExecutingContext>(MockBehavior.Loose);
        this.Http = new Mock<HttpContextBase>(MockBehavior.Loose);
        this.Server = new Mock<HttpServerUtilityBase>(MockBehavior.Loose);
        this.Response = new Mock<HttpResponseBase>(MockBehavior.Loose);
        this.Request = new Mock<HttpRequestBase>(MockBehavior.Loose);
        this.Session = new Mock<HttpSessionStateBase>(MockBehavior.Loose);
        this.Cookies = new HttpCookieCollection();

        this.RoutingRequestContext.SetupGet(c => c.HttpContext).Returns(this.Http.Object);
        this.ActionExecuting.SetupGet(c => c.HttpContext).Returns(this.Http.Object);
        this.Http.SetupGet(c => c.Request).Returns(this.Request.Object);
        this.Http.SetupGet(c => c.Response).Returns(this.Response.Object);
        this.Http.SetupGet(c => c.Server).Returns(this.Server.Object);
        this.Http.SetupGet(c => c.Session).Returns(this.Session.Object);
        this.Request.Setup(c => c.Cookies).Returns(Cookies);
    }

}

In the constructor I new up the new mocks and do some wiring up of the dependencies of the objects you will need while working with the HttpContext.  Since the above action uses a cookie, note that there is a Cookies property that is mapped to the mocked request object.  So when Request.Cookies is called in the action method it will actually be looking at the Cookies collection defined in this class.

Here is a test that uses the MockContext object to test the action method above.

[TestMethod]
public void TestActionCookieValueReturnedInModel()
{
   //arrange
    var expectedValue = "TEST";
    MockContext mockContext = new MockContext();
    mockContext.Cookies.Add(new HttpCookie("ID", expectedValue));
    var homeController = new HomeController()
        {
            ControllerContext = new ControllerContext()
                {
                    HttpContext = mockContext.Http.Object
                }
        };

    //act
    var result = homeController.TestAction() as ViewResult;
    var model = result.ViewData.Model as TestActionViewModel;

    //assert
    Assert.AreEqual(expectedValue, model.Id);
}

Note that I new up a new instance of the MockContext and add the cookie to the cookie collection so the action method above will have access to it.  When creating an instance of the controller I used an object initializer to set the controller context to a new controller context using our MockContext http object.

Using this technique it would be very easy to mock out what would happen if the cookie was not in the cookies collection.

6 comments:

  1. Worked like a charm. How to handle query strings?

    ReplyDelete
  2. Hi Dave, this has helped me a bunch! I have modified it a bit though, would you mind if I publish it to my blog (giving credit to you, of course)?

    ReplyDelete
  3. Jeff, I wouldn't mind at all. If you could add a link to it when you are done that would be awesome. :)

    ReplyDelete
  4. Thanks for the post.Was struggling with Rhino Mocks and mvcContrib. Nice work :)

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete