Search
Categories
Navigation
Twitter Updates

Twitter Updates

    follow me on Twitter
    « Controller Scope & Testing | Main | Are Your Controllers Playing Music? »
    Friday
    Sep182009

    Mocking HttpContext with Moq

    Increased testability is one of the many benefits often touted by ASP.NET MVC advocates. While it’s true that MVC abstracts away a lot of the underlying ASP.NET runtime, it’s still highly likely that your controllers will poke around in HttpContext from time to time. When they do, your unit tests will typically need to interact with a stub or mock object for test isolation purposes.

    Creating these objects can be a bit of a pain because HttpContextBase has a lot of stuff in it, but Moq’s recursive mocking functionality actually makes this super easy to do. Unfortunately, judging by the Google results, this seems to be a relatively unknown feature. Daniel Cazzulino (a Moq maintainer) posted about this capability way back in October of 2008, but I thought I’d also walk through it in hopes of making this information more discoverable.

    The Scenario

    Let’s say we’ve got the following action method which we’re trying to test:

    public ActionResult Index()
    {
        var secret = string.Empty;
    
        if (Session["secret"] is string)
        {
            secret = (string) Session["secret"];
        }
    
        ViewData["secret"] = secret;
    
        return View();
    }

    If we test this with a freshly instantiated controller and no stub objects, the call to Session[“secret”] will throw a null reference exception. To get around this, we need to provide the controller with a ControllerContext, passing it an instance of HttpContextBase which it will then use to wire up several properties, including Session.

    Once we have the HttpContextBase stub object, we need to setup a return value for the call to Session. Thankfully, rather than creating all the components of the HttpContextBase class, we can have Moq do it for us and just setup the calls to return what we want:

    [Fact]
    public void Index_SetsViewDataSecretToEmptyString_WhenNoSecretInSession()
    {
        string expected = null;
        var httpContextStub = new Mock<HttpContextBase>
        {
            DefaultValue = DefaultValue.Mock
        };
        var sessionStub = Mock.Get(httpContextStub.Object.Session);
        sessionStub.Setup(s => s["secret"]).Returns(expected);
        
        var controller = new HomeController();
        controller.ControllerContext = new ControllerContext(
            httpContextStub.Object, new RouteData(), controller);
    
        var result = (ViewResult)controller.Index();
        var data = result.ViewData["secret"];
    
        Assert.Equal(string.Empty, data);
    }

    By setting the DefaultValue property on the Mock<T> object to DefaultValue.Mock (line 7), we’re informing Moq that we want the framework to dynamically create stub objects for all of its mock-able methods and properties, should they be called. This saves us the time of manually creating stubs for all the objects we need to manipulate inside the HttpContextBase object. For such a simple unit test, we could also just use a recursive mock to get to the session object (see Daniel’s post) but the DefaultValue approach is a bit more flexible, as it’ll dynamically fake objects should the controller start using additional context data in the future.

    To make our lives even easier, Moq provides the Mock.Get() method where we can pass in a mocked object and get the underlying Mock<T>. This allows us to rely on Moq to create all the component instances that we need, and just setup expectations or behavior as necessary for the test (line 10).

    To streamline this further, I typically pull the stub creation code into a helper method, so that the final unit test code is a bit more streamlined:

    public ControllerContext CreateStubControllerContext(Controller controller)
    {
        var httpContextStub = new Mock<HttpContextBase>
                              {
                                  DefaultValue = DefaultValue.Mock
                              };
        return new ControllerContext(
            httpContextStub.Object, new RouteData(), controller);
    }
    
    [Fact]
    public void Index_SetsViewDataSecretToSessionValue_WhenSetInSession()
    {
        var expected = "abc";
    
        var controller = new HomeController();
        controller.ControllerContext =
            CreateStubControllerContext(controller);
        
        var sessionStub = Mock.Get(controller.Session);
        sessionStub.Setup(s => s["secret"]).Returns(expected);
    
        var result = (ViewResult)controller.Index();
        var data = result.ViewData["secret"];
    
        Assert.Equal(expected, data);
    }

    Wrapping Up

    This Moq capability is powerful, especially when your controllers begin doing more poking around inside a number of HttpContext members. In more complex tests, you may also wish to use a stub for RouteData within the ControllerContext, or simply use a stub of ControllerContext itself,and the same approach still applies.

    Hopefully, this post makes finding out about this Moq feature a little bit easier.

    Notes & Disclaimers

    There are some objects that Moq can’t stub for you (such as HttpRequestBase.Params), so occasionally you’ll still need to create fake objects on your own, but this applies regardless of how you create the stub objects that Moq can create.

    A quick note regarding the terminology used in this post: I’ve tried to use more formal terms for dynamic stub objects (which provide canned data to the code under test) and dynamic mock objects (which have expectations that are typically validated as the test assert). The one notable exception to this was in the post title, where I’ve used the terminology I expect people to search on. You can read more in Martin Fowler’s Mocks Aren’t Stubs article.

    This post was written using Visual Studio 2008, ASP.NET MVC 1.0, .NET 3.5, xUnit.NET 1.5 and Moq 4.0.

    PrintView Printer Friendly Version

    EmailEmail Article to Friend