Mocking HttpContext when testing IActionFilters in MVC

While trying to figure out how to make a full featured implementation of ContextMock builder based solely on Moq in order to make unit tests for an implementation of IActionFilter I stumbled upon a problem :

System.NotSupportedException Invalid setup on a non-virtual (overridable in VB) member: x => x.Values

Then I read some Q&A on stackoverflow here and here and some others in which in summary is stated that :

Moq cannot mock non-virtual methods and sealed classes.

I really wanted to keep using Mock<T> mainly because

  1. it matches nicely with builder pattern which I like to use for unit testing harness.
  2. it has Verify methods which is great for the “assert” section of the unit test.

But because I did not manage to get it through I made another implementation based on .NET classes rather then Mock<.Net classes>. The implementation is suitable for extension and serves the needs for “arranging” the HttpContext for the unit test of the IActionFilter. So here it is :

using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.SessionState;
using Moq;

namespace Web.Infrastructure.UnitTests
{
    public class ContextMockBuilder
    {
        private readonly HttpRequest _httpRequest;
        private readonly HttpContext _httpContext;
        private readonly HttpContextWrapper _httpContextWrapper;
        private readonly ControllerContext _controllerContext;
        private readonly ActionExecutingContext _actionExecutingContext;
        public Controller OnController { get; private set; }
        public Mock<IPrincipal> User { get; private set; } 
        private IPrincipal _user { get; set; }

        public ContextMockBuilder(Controller controller = null)
        {
            _httpRequest = new HttpRequest("", "http://stackoverflow/", "");
            var stringWriter = new StringWriter();
            var httpResponse = new HttpResponse(stringWriter);
            _httpContext = new HttpContext(_httpRequest, httpResponse);
            _httpContextWrapper = new HttpContextWrapper(_httpContext);
            _controllerContext = new ControllerContext(_httpContextWrapper,new RouteData(), controller ?? new FakeController());
            _actionExecutingContext = new ActionExecutingContext(_controllerContext, new FakeActionDescriptor(), new Dictionary<string, object>());

            var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
                                                    new HttpStaticObjectsCollection(), 10, true,
                                                    HttpCookieMode.AutoDetect,
                                                    SessionStateMode.InProc, false);

            _httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
                                        BindingFlags.NonPublic | BindingFlags.Instance,
                                        null,
                                        CallingConventions.Standard,
                                        new[] { typeof(HttpSessionStateContainer) },
                                        null)
                                .Invoke(new object[] { sessionContainer });
        }

        public HttpContextBase BuildHttpContextBase()
        {
            return this._httpContextWrapper;
        }

        public ControllerContext BuildControllerContext()
        {
            return this._controllerContext;
        }

        public ActionExecutingContext BuildActionExecutingContext()
        {
            return this._actionExecutingContext;
        }

        public ContextMockBuilder WithRouteDataValues(IDictionary<string,string> routeValueDictionary )
        {
            _httpRequest.RequestContext.RouteData.Values.Clear();
            _controllerContext.RouteData.Values.Clear();

            foreach (var rv in routeValueDictionary)
            {
                _httpRequest.RequestContext.RouteData.Values.Add(rv.Key,rv.Value);
                _controllerContext.RouteData.Values.Add(rv.Key, rv.Value);
            }

            return this;
        }

        public ContextMockBuilder WithPrincipal(string username, int userid, string[] roles)
        {
            GenericIdentity identity = new GenericIdentity(username);
            _user = new GenericPrincipal(
                    identity,
                    roles
                    );

            _httpContext.User = _user;
            return this;
        }
        private class FakeSessionState : HttpSessionStateBase
        {
            Dictionary<string, object> items = new Dictionary<string, object>();
            public override object this[string name]
            {
                get { return items.ContainsKey(name) ? items[name] : null; }
                set { items[name] = value; }
            }
        }

        private class FakeController : Controller
        {
            
        }

        private class FakeActionDescriptor : ActionDescriptor 
        {
            public override object Execute(ControllerContext controllerContext, IDictionary<string, object> parameters)
            {
                throw new System.NotImplementedException();
            }

            public override ParameterDescriptor[] GetParameters()
            {
                throw new System.NotImplementedException();
            }

            public override string ActionName { get; }
            public override ControllerDescriptor ControllerDescriptor { get; }
        }
    }
}