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
- it matches nicely with builder pattern which I like to use for unit testing harness.
- 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; }
}
}
}