Can't seem to set object when mocking HttpApplicationState with Moq

2.1k views Asked by At

I have an action filter which is setting an object in the HttpApplicationState of the HttpContext of the filter context. I would like to have this functionality in a unit test but for some reason the object is not being set in the underlying NameObjectCollectionBase from which HttpApplicationState derives.

I know the functionality works because when I run the MVC application it works as expected.

How can I set up my test to get it to set objects in the application state? Im using Moq and here is some of the code so far. It fails on

Asset.IsNotNull(context.HttpContext.Application["config"]);

Here is the code.

public override void OnActionExecuted(ActionExecutedContext filterContext)
{
    ControllerBase controller = filterContext.Controller;
    if (!(controller is ApplicationController))
        return;

    ApplicationController applicationController = (ApplicationController) controller;

    IDictionary<string, string> config;

    // Loads the view configuration values.
    if (filterContext.HttpContext.Application["config"] == null)
    {
        config = applicationController.ApplicationService.GetConfiguration();
        filterContext.HttpContext.Application["config"] = config;
    }
    else
    {
        config = (IDictionary<string, string>) filterContext.HttpContext.Application["config"];
    }

    applicationController.ViewBag.BlogTitle = AddConfigurationValueToViewBag("BlogTitle", config);

}

Here's the test so far.

[TestMethod]
public void ApplicationAttribute_OnActionExecuted_SetsConfigurationDctionaryInAppicationCache()
{
    // Arrange
    Mock<HttpContextBase> httpContext = new Mock<HttpContextBase>();

    var mockApplicationState = new Mock<HttpApplicationStateBase>();
    httpContext.Setup(h => h.Application).Returns(mockApplicationState.Object);

    ApplicationController applicationController = new BlogController(null, null, MocksAndStubs.CreateMockApplicationService());

    Mock<ActionExecutedContext> actionExecutedContext = new Mock<ActionExecutedContext>();
    actionExecutedContext.SetupGet(c => c.HttpContext).Returns(httpContext.Object);
    actionExecutedContext.SetupGet(c => c.Controller).Returns(applicationController);

    // Act
    ApplicationAttribute applicationAttribute = new ApplicationAttribute();
    ActionExecutedContext context = actionExecutedContext.Object;
    applicationAttribute.OnActionExecuted(context);

    // Assert
    Assert.IsNotNull(context.HttpContext.Application["config"]);
}
2

There are 2 answers

3
Phil Sandler On BEST ANSWER

It looks like you are expecting your mock object to more or less act like the real object. It doesn't work that way; the mock will only do what you tell it to do, nothing more, nothing less. If you don't tell the mock to return something specific when you call . . .

context.HttpContext.Application["config"]

. . . then it simply won't. If you did set the mock up to return something specific, it would defeat the purpose of your test.

Without any additional understanding or your situation (the "what" and "why" of your testing stategy), it looks like you are trying to ensure that the set on the application state gets called. I would recommend doing a mockApplicationState.Verify() as your assertion to test that the set happened, instead of testing the resulting state of the object itself.

Edit: Verify() allows you to assert/ensure that a method (or property method) was called, with or without conditions. This should get you started:

http://code.google.com/p/moq/wiki/QuickStart#Verification

So your verify would look something like (totally untested!):

mockApplicationState.Verify(x => x["config"] == [expected value], Times.Once());

This basically says fail the test if the mockApplicationState["config"] got set to the expected value less than once or more than once.

I'm assuming HttpApplicationStateBase is not sealed. If it is then the above may throw an exception.

0
Spencer On

As an alternative to MOQ I often in this scenario generate some stubs derived from the base classes in System.Web.Abstractions. I often use this technique for MVC applications as MVC / WebApi controllers contain an abstraction to HttpContext (HttpContextBase)

This way I can stub out HttpContext requirements in my unit / integration tests, here's a sample...

public class MockHttpApplicationState : HttpApplicationStateBase
{
    private IDictionary<string, object> _appState = new Dictionary<string, object>();

    public override void Add(string name, object value)
    {
        _appState.Add(name, value);
    }

    public override object Get(string name)
    {
        return _appState[name];
    }

    public override object this[string name]
    {
        get
        {
            return _appState[name];
        }

        set
        {
            _appState[name] = value;
        }
    }
}

public class MockHttpContext : HttpContextBase
{
    private IDictionary<string, object> _appKeys;

    public MockHttpContext()
    {

    }

    /// <summary>
    /// Accepts a dictionary of app keys to supply to the HttpApplicationState instance
    /// </summary>
    /// <param name="applicationState"></param>
    public MockHttpContext(IDictionary<string,object> applicationState)
    {
        _appKeys = applicationState;
    }

    public override Cache Cache
    {
        get
        {                
            return HttpRuntime.Cache;
        }
    }

    public override HttpApplicationStateBase Application
    {
        get
        {
            var mockAppState = new MockHttpApplicationState();

            foreach (string key in _appKeys.Keys)
            {
                mockAppState.Add(key, _appKeys[key]);
            }

            return mockAppState;
        }
    }

    public override HttpRequestBase Request
    {
        get
        {
            return new HttpRequestWrapper(new HttpRequest(null,"http://localhost",null));
        }
    }
}

Then my test can establish Controller and Http Context:

private readonly OnlineShop.MVC.Controllers.HomeController _controller = 
        new MVC.Controllers.HomeController(null,new UnitOfWork());

    [OneTimeSetUp]
    public void Init()
    {
        var appKeys = new Dictionary<string, object>();

        appKeys.Add("localhost", 1);

        var httpContext = new MockHttpContext(appKeys);

        _controller.ControllerContext = new ControllerContext()
        {
            Controller = _controller,
            RequestContext = new RequestContext(httpContext, new RouteData())    
        };                        
    }

    [Test]
    public void Index_Returns_HomeView()
    {            
        var view = _controller.Index() as ViewResult;
        var viewModel = view.Model as MVC.ViewModels.Home;

        Assert.IsInstanceOf<OnlineShop.MVC.ViewModels.Home>(viewModel);
        Assert.IsTrue(viewModel.FeaturedProducts.Count > 0);
    }

And my controller is aware of it's ambient HttpContextBase instance supplying Cache and Application state:

  public ActionResult Index()
    {                        
        string cacheKey = string.Format("FeaturedProducts-{0}",WebsiteId);
        IList<Product> productList = this.HttpContext.Cache[cacheKey] as IList<Product>;


        //My app keeps a list of website contexts in the Application. This test returns 1 based on the unit / int tests or a real world db value when hosted on IIS etc..
        int websiteId = (int)HttpContext.Application[this.Request.Url.Host];