Skip to content

Instantly share code, notes, and snippets.

@bronumski
Created August 21, 2020 03:28
Show Gist options
  • Save bronumski/f85cb9fa3e2f86002e84da081405b6b0 to your computer and use it in GitHub Desktop.
Save bronumski/f85cb9fa3e2f86002e84da081405b6b0 to your computer and use it in GitHub Desktop.
ASP.NET MVC 5 Unit Testing
abstract class SpecsForController<TSut, TResult> where TSut : ControllerBase where TResult : ActionResult
{
private readonly NSubstituteMockingKernel kernel = new NSubstituteMockingKernel();
private TSut Subject { get; set; }
protected TResult Result { get; private set; }
protected HttpContextBase HttpContext => Subject.ControllerContext.HttpContext;
protected virtual void Arrange(ControllerContext context) { }
protected abstract Expression<Action<TSut>> Act { get; }
protected TService ResolveDependency<TService>() => kernel.Get<TService>();
protected void RegisterDependency<TDependency>(TDependency dependency)
=> kernel.Bind<TDependency>().ToConstant(dependency);
protected virtual HttpMethod Method => HttpMethod.Get;
[OneTimeSetUp]
public void SetUp()
{
var httpContextBase = Substitute.For<HttpContextBase>();
httpContextBase.Request.QueryString.Returns(new NameValueCollection());
httpContextBase.Request.Cookies.Returns(new HttpCookieCollection());
httpContextBase.Request.HttpMethod.Returns(Method.Method);
httpContextBase.Request.IsAuthenticated.Returns(x => httpContextBase.User?.Identity.IsAuthenticated == true);
var controllerContext = new ControllerContext
{
RouteData = new RouteData(),
HttpContext = httpContextBase,
};
System.Web.HttpContext.Current = new HttpContext(
new HttpRequest("", "http://tempuri.org", "")
{
Browser = new HttpBrowserCapabilities
{
Capabilities = new Dictionary<string, string> { { "supportsEmptyStringInCookieValue", "false" }, { "cookies", "false" } }
}
},
new HttpResponse(new StringWriter()));
kernel.Bind<ILogPerformanceRepository>().To<DummyLogPerformanceRepository>();
Arrange(controllerContext);
var controller = kernel.Get<TSut>();
controllerContext.Controller = controller;
controller.ControllerContext = controllerContext;
Subject = controller;
var actionInvoker = new ControllerSpecActionInvoker<TResult>(Act.Body);
var methodName = ExtractActionName(Act.Body);
actionInvoker.InvokeAction(Subject.ControllerContext, methodName);
Result = actionInvoker.Result;
}
private static string ExtractActionName(Expression body)
{
switch (body)
{
case MethodCallExpression e: return e.Method.Name;
case UnaryExpression e: return ExtractActionName(e.Operand);
default: throw new Exception("Cannot determine action from action");
}
}
~SpecsForController() => kernel.Dispose();
class DummyLogPerformanceRepository : ILogPerformanceRepository
{
public void LogToDatabase(MethodPerformance methodPerformance)
{
throw new NotImplementedException();
}
public T LogPerformanceForMethod<T>(Expression<Func<T>> callbackExpression) => callbackExpression.Compile()();
}
class ControllerSpecActionInvoker<TResult> : ControllerActionInvoker where TResult : ActionResult
{
private readonly Expression body;
public ControllerSpecActionInvoker(Expression body)
{
this.body = body;
}
public TResult Result { get; private set; }
protected override void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
=> Result = actionResult as TResult;
protected override IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (body is MethodCallExpression methodCall)
{
return methodCall.Method.GetParameters()
.Zip(methodCall.Arguments.Select(GetArgumentAsConstant), (param, arg) => new { param.Name, Value = ChangeType(arg.Value, param.ParameterType) })
.ToDictionary(item => item.Name, item => item.Value);
}
return base.GetParameterValues(controllerContext, actionDescriptor);
}
private ConstantExpression GetArgumentAsConstant(Expression exp)
{
switch (exp)
{
case ConstantExpression constExp:
return constExp;
case UnaryExpression uranExp:
return GetArgumentAsConstant(uranExp.Operand);
}
throw new NotSupportedException($"Cannot handle expression of type '{exp.GetType()}'");
}
private static object ChangeType(object value, Type conversion)
{
var t = conversion;
if (!t.IsGenericType || t.GetGenericTypeDefinition() != typeof(Nullable<>)) return Convert.ChangeType(value, t);
if (value == null) return null;
t = Nullable.GetUnderlyingType(t);
return Convert.ChangeType(value, t);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment