WebApi2 中的单元测试属性路由
Unit testing attribute routing in WebApi2
我正在处理一个 WebApi2 属性路由项目,我正在尝试对路由进行单元测试(请求正在控制器中执行正确的 api 方法)。但是到目前为止我无法让它工作...
它似乎没有获取属性中定义的路由
知道可能有什么问题或遗漏了什么吗?
提前致谢!吉列尔莫.
这是我的单元测试代码:
var config = new HttpConfiguration
{
IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always,
};
config.Routes.MapHttpRoute(name: "Default", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional });
request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/homes/report");
route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
routeData = new HttpRouteData(route, new HttpRouteValueDictionary
{
{ "controller", "homes" },
});
config.MapHttpAttributeRoutes();
config.EnsureInitialized();
controller = new HomesController
{
Catalog = catalog.Object,
ControllerContext = new HttpControllerContext(config, routeData, request),
Request = request,
};
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
var routeTester = new RouteTester(config, request, controller.ControllerContext);
Assert.IsTrue(routeTester.CompareSignatures(ReflectionHelpers.GetMethodInfo((HomesController p) => p.GetHomesReport())));
RouteTester.cs
public class RouteTester
{
readonly HttpConfiguration config;
readonly HttpRequestMessage request;
readonly IHttpRouteData routeData;
readonly IHttpControllerSelector controllerSelector;
readonly HttpControllerContext controllerContext;
public RouteTester(HttpConfiguration conf, HttpRequestMessage req, HttpControllerContext context)
{
config = conf;
request = req;
controllerContext = context;
routeData = context.RouteData;
controllerSelector = new DefaultHttpControllerSelector(config);
}
public string GetActionName()
{
if (controllerContext.ControllerDescriptor == null)
GetControllerType();
var actionSelector = new ApiControllerActionSelector();
var descriptor = actionSelector.SelectAction(controllerContext);
return descriptor.ActionName;
}
public Type GetControllerType()
{
var descriptor = controllerSelector.SelectController(request);
controllerContext.ControllerDescriptor = descriptor;
return descriptor.ControllerType;
}
public bool CompareSignatures(MethodInfo method)
{
if (controllerContext.ControllerDescriptor == null)
GetControllerType();
var actionSelector = new ApiControllerActionSelector();
var x = actionSelector.GetActionMapping(controllerContext.ControllerDescriptor)[request.Method.ToString()];
return x.Any(item => ((MethodBase)(((ReflectedHttpActionDescriptor)item).MethodInfo)).ToString() == ((MethodBase)method).ToString());
}
ReflectionHelpers.cs
public class ReflectionHelpers
{
public static string GetMethodName<T, U>(Expression<Func<T, U>> expression)
{
var method = expression.Body as MethodCallExpression;
if (method != null)
return method.Method.Name;
throw new ArgumentException("Expression is wrong");
}
public static MethodInfo GetMethodInfo<T, U>(Expression<Func<T, U>> expression)
{
var method = expression.Body as MethodCallExpression;
if (method != null)
return method.Method;
throw new ArgumentException("Expression is wrong");
}
public static MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
{
var method = expression.Body as MethodCallExpression;
if (method != null)
return method.Method;
throw new ArgumentException("Expression is wrong");
}
}
控制器片段
[Route("api/homes/homereport")]
public void GetHomesReport()
{
var homeReportItems = HomeReport();
}
不确定这是否正是您问题的解决方案,但我使用这种方法来控制属性路由。
它强制您指定控制器 class 和方法签名,但都没有字符串文字,因此您可以在此处获得良好的编译时间验证。一个简单方法的 FunctionSignature 作为 public 字符串 Action(int value)
它看起来像这样 Func<int, string>
.
测试方法如下所示:
public void TestMethod() {
var info = GetEndpointInfo<ControllerClass, FunctionSignature>(c => c.Action);
Assert.IsNotNull(info.AllowsAnonymous);
Assert.AreEqual("data/places/{query}", info.Route);
}
这是获取元信息的一些 lambda 和反射暴力:
private EndpointInfo GetEndpointInfo<TController, TMethod>(Expression<Func<TController, TMethod>> expression) {
var controllerType = typeof(TController);
var prefix = controllerType.GetCustomAttribute<RoutePrefixAttribute>().Prefix;
MethodInfo methodInfo = GetMemberInfo(expression);
var template = methodInfo.GetCustomAttribute<RouteAttribute>().Template;
var info = new EndpointInfo() {
AllowsAnonymous = controllerType.GetCustomAttribute<AllowAnonymousAttribute>() != null, //etend here to check method level attr
Route = prefix + "/" + template
};
return info;
}
private MethodInfo GetMemberInfo<TController, TMethod>(Expression<Func<TController, TMethod>> expression) {
var unaryExpression = expression.Body as UnaryExpression;
var methodCall = unaryExpression.Operand as MethodCallExpression;
var constant = methodCall.Object as ConstantExpression;
var methodInfo = constant.Value as MethodInfo;
return methodInfo;
}
class EndpointInfo{
public bool AllowsAnonymous { get; set; }
public string Route { get; set; }
}
希望对您有所帮助。
我正在处理一个 WebApi2 属性路由项目,我正在尝试对路由进行单元测试(请求正在控制器中执行正确的 api 方法)。但是到目前为止我无法让它工作...
它似乎没有获取属性中定义的路由 知道可能有什么问题或遗漏了什么吗?
提前致谢!吉列尔莫.
这是我的单元测试代码:
var config = new HttpConfiguration
{
IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always,
};
config.Routes.MapHttpRoute(name: "Default", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional });
request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/api/homes/report");
route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
routeData = new HttpRouteData(route, new HttpRouteValueDictionary
{
{ "controller", "homes" },
});
config.MapHttpAttributeRoutes();
config.EnsureInitialized();
controller = new HomesController
{
Catalog = catalog.Object,
ControllerContext = new HttpControllerContext(config, routeData, request),
Request = request,
};
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
var routeTester = new RouteTester(config, request, controller.ControllerContext);
Assert.IsTrue(routeTester.CompareSignatures(ReflectionHelpers.GetMethodInfo((HomesController p) => p.GetHomesReport())));
RouteTester.cs
public class RouteTester
{
readonly HttpConfiguration config;
readonly HttpRequestMessage request;
readonly IHttpRouteData routeData;
readonly IHttpControllerSelector controllerSelector;
readonly HttpControllerContext controllerContext;
public RouteTester(HttpConfiguration conf, HttpRequestMessage req, HttpControllerContext context)
{
config = conf;
request = req;
controllerContext = context;
routeData = context.RouteData;
controllerSelector = new DefaultHttpControllerSelector(config);
}
public string GetActionName()
{
if (controllerContext.ControllerDescriptor == null)
GetControllerType();
var actionSelector = new ApiControllerActionSelector();
var descriptor = actionSelector.SelectAction(controllerContext);
return descriptor.ActionName;
}
public Type GetControllerType()
{
var descriptor = controllerSelector.SelectController(request);
controllerContext.ControllerDescriptor = descriptor;
return descriptor.ControllerType;
}
public bool CompareSignatures(MethodInfo method)
{
if (controllerContext.ControllerDescriptor == null)
GetControllerType();
var actionSelector = new ApiControllerActionSelector();
var x = actionSelector.GetActionMapping(controllerContext.ControllerDescriptor)[request.Method.ToString()];
return x.Any(item => ((MethodBase)(((ReflectedHttpActionDescriptor)item).MethodInfo)).ToString() == ((MethodBase)method).ToString());
}
ReflectionHelpers.cs
public class ReflectionHelpers
{
public static string GetMethodName<T, U>(Expression<Func<T, U>> expression)
{
var method = expression.Body as MethodCallExpression;
if (method != null)
return method.Method.Name;
throw new ArgumentException("Expression is wrong");
}
public static MethodInfo GetMethodInfo<T, U>(Expression<Func<T, U>> expression)
{
var method = expression.Body as MethodCallExpression;
if (method != null)
return method.Method;
throw new ArgumentException("Expression is wrong");
}
public static MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
{
var method = expression.Body as MethodCallExpression;
if (method != null)
return method.Method;
throw new ArgumentException("Expression is wrong");
}
}
控制器片段
[Route("api/homes/homereport")]
public void GetHomesReport()
{
var homeReportItems = HomeReport();
}
不确定这是否正是您问题的解决方案,但我使用这种方法来控制属性路由。
它强制您指定控制器 class 和方法签名,但都没有字符串文字,因此您可以在此处获得良好的编译时间验证。一个简单方法的 FunctionSignature 作为 public 字符串 Action(int value)
它看起来像这样 Func<int, string>
.
测试方法如下所示:
public void TestMethod() {
var info = GetEndpointInfo<ControllerClass, FunctionSignature>(c => c.Action);
Assert.IsNotNull(info.AllowsAnonymous);
Assert.AreEqual("data/places/{query}", info.Route);
}
这是获取元信息的一些 lambda 和反射暴力:
private EndpointInfo GetEndpointInfo<TController, TMethod>(Expression<Func<TController, TMethod>> expression) {
var controllerType = typeof(TController);
var prefix = controllerType.GetCustomAttribute<RoutePrefixAttribute>().Prefix;
MethodInfo methodInfo = GetMemberInfo(expression);
var template = methodInfo.GetCustomAttribute<RouteAttribute>().Template;
var info = new EndpointInfo() {
AllowsAnonymous = controllerType.GetCustomAttribute<AllowAnonymousAttribute>() != null, //etend here to check method level attr
Route = prefix + "/" + template
};
return info;
}
private MethodInfo GetMemberInfo<TController, TMethod>(Expression<Func<TController, TMethod>> expression) {
var unaryExpression = expression.Body as UnaryExpression;
var methodCall = unaryExpression.Operand as MethodCallExpression;
var constant = methodCall.Object as ConstantExpression;
var methodInfo = constant.Value as MethodInfo;
return methodInfo;
}
class EndpointInfo{
public bool AllowsAnonymous { get; set; }
public string Route { get; set; }
}
希望对您有所帮助。