Last active
December 14, 2015 11:39
-
-
Save bryanmenard/5080830 to your computer and use it in GitHub Desktop.
RouteTestsBase - Base class for testing routes in ASP.NET MVC using action-centric tests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Release under MIT license Copyright (c) 2013 Bryan Menard, http://www.bryblog.com/license.txt | |
using System; | |
using System.Linq; | |
using System.Linq.Expressions; | |
using System.Reflection; | |
using System.Web; | |
using System.Web.Mvc; | |
using System.Web.Routing; | |
using NUnit.Framework; | |
namespace MyApp.Web.UnitTests.Routes | |
{ | |
/// <summary> | |
/// Base class for route testing | |
/// </summary> | |
public class RouteTestsBase<TController> where TController : class, IController | |
{ | |
private StubHttpContextForRouting Context; | |
private RouteCollection Routes; | |
private string RequestUrl; | |
private TController T4MvcController; | |
private readonly string ControllerName = GetControllerName(); | |
[SetUp] | |
public void SetUpBase() | |
{ | |
Routes = new RouteCollection(); | |
RegisterRoutes(Routes); | |
T4MvcController = GetT4Controller(); | |
SetUp(); | |
} | |
private TController GetT4Controller() | |
{ | |
const BindingFlags staticFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; | |
var controllerField = typeof(MVC).GetFields(staticFlags).SingleOrDefault(x => x.FieldType == typeof(TController)); | |
if (controllerField != null) | |
{ | |
return (TController)controllerField.GetValue(null); | |
} | |
// We need to check for Areas | |
// Find the eligible areas | |
var areas = typeof(MVC).GetProperties(staticFlags) | |
.Where(x => !typeof(IController).IsAssignableFrom(x.PropertyType)) // Find non-controller properties | |
.Select(x => x.GetValue(null)) | |
.ToList(); | |
var controller = areas.Select(x => | |
{ | |
var property = x.GetType().GetFields().SingleOrDefault(y => y.FieldType == typeof(TController)); | |
if (property != null) return (TController)property.GetValue(x); | |
return (TController)null; | |
}).SingleOrDefault(x => x != null); | |
if (controller == null) | |
throw new InvalidOperationException("Unable to find T4MVC controller associated with this controller."); | |
return controller; | |
} | |
/// <summary> | |
/// Runs text-specific setup steps after base setup has executed | |
/// </summary> | |
protected virtual void SetUp() { } | |
private void RegisterRoutes(RouteCollection routes) | |
{ | |
// Register areas | |
var areaTypes = typeof(MvcApplication).Assembly.GetTypes().Where(x => typeof(AreaRegistration).IsAssignableFrom(x)).ToList(); | |
var areas = areaTypes.Select(x => (AreaRegistration)Activator.CreateInstance(x, nonPublic: true)).ToList(); | |
foreach (var area in areas) | |
{ | |
var areaContext = new AreaRegistrationContext(area.AreaName, routes); | |
area.RegisterArea(areaContext); | |
} | |
// Register main routes | |
MvcApplication.RegisterRoutes(routes); | |
} | |
protected void Url(string requestUrl) | |
{ | |
if (!requestUrl.StartsWith("/")) requestUrl = "/" + requestUrl; | |
RequestUrl = requestUrl; | |
Context = new StubHttpContextForRouting(requestUrl); | |
} | |
private UrlHelper GetUrlHelper(string appPath = "/", RouteCollection routes = null) | |
{ | |
if (routes == null) | |
{ | |
routes = new RouteCollection(); | |
MvcApplication.RegisterRoutes(routes); | |
} | |
HttpContextBase httpContext = new StubHttpContextForRouting(appPath); | |
RouteData routeData = new RouteData(); | |
routeData.Values.Add("controller", "defaultcontroller"); | |
routeData.Values.Add("action", "defaultaction"); | |
RequestContext requestContext = new RequestContext(httpContext, routeData); | |
UrlHelper helper = new UrlHelper(requestContext, routes); | |
return helper; | |
} | |
protected T Ignore<T>() | |
{ | |
return default(T); | |
} | |
protected void AssertRoute(Expression<Func<TController, ActionResult>> actionExpression) | |
{ | |
var methodExpression = actionExpression.Body as MethodCallExpression; | |
if (methodExpression == null) throw new ArgumentException("The expression must be a method call to an Action."); | |
var routeData = Routes.GetRouteData(Context); | |
if (routeData == null) throw new InvalidOperationException("GetRouteData should return a non-null value."); | |
var method = methodExpression.Method; | |
Assert.That(ControllerName, Is.EqualTo(routeData.Values["controller"])); | |
var action = method.Name; | |
Assert.That(action, Is.EqualTo(routeData.Values["action"])); | |
var arguments = methodExpression.Arguments.Zip(method.GetParameters(), (arg, param) => | |
{ | |
object value; | |
bool ignore = false; | |
var constantArg = arg as ConstantExpression; | |
if (constantArg != null) | |
{ | |
value = constantArg.Value; | |
} | |
else if (arg is MethodCallExpression) | |
{ | |
ignore = true; | |
value = null; | |
} | |
else | |
{ | |
throw new InvalidOperationException("The Action arguments must be a constant or a method call to Ignore."); | |
} | |
return new { Value = value, Name = param.Name, Ignore = ignore }; | |
}); | |
foreach (var argument in arguments.Where(x => !x.Ignore)) | |
{ | |
Assert.That(argument.Value.ToString(), Is.EqualTo(routeData.Values[argument.Name]), string.Format("Argument \"{0}\" does not have the correct value", argument.Name)); | |
} | |
// Assert the outbound | |
var outboundAction = actionExpression.Compile().Invoke(T4MvcController); | |
var outboundUrl = GetUrlHelper(routes: Routes).Action(outboundAction); | |
Assert.That(outboundUrl, Is.EqualTo(RequestUrl)); | |
} | |
private static string GetControllerName() | |
{ | |
var name = typeof (TController).Name; | |
if (name.EndsWith("Controller")) | |
name = name.Substring(0, name.Length - "Controller".Length); | |
return name; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment