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; }
}

希望对您有所帮助。