4

Closed

Improve routing testability in Web API.

description

Scenario:
A user has created a skeletal structure of ApiControllers and actions. Currently the user does not have any implementation yet in them. User creates some routes and likes to test if the expected action is hit for a given request message.

Currently for testing the above scenario, a user would need to know bunch of inner apis like ControllerDescriptor, ActionSelector etc.(Example below) This introduces complexity to the end user. Given that this is a very basic scenario that users could hit, we should try to improve the testability of it.

FYI...following is the sample test code that i had to write currently:

HttpConfiguration testGlobalConfiguration = new HttpConfiguration(new HttpRouteCollection("TestApp"));
        WebApiConfig.Register(testGlobalConfiguration);

        HttpRequestMessage request = new HttpRequestMessage(method, requestUrl);

        IHttpRouteData routeData = testGlobalConfiguration.Routes.GetRouteData(request);
        request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;

        DefaultHttpControllerSelector controllerSelector = new DefaultHttpControllerSelector(testGlobalConfiguration);

        HttpControllerDescriptor controlleDescriptor = controllerSelector.SelectController(request);

        HttpControllerContext controllerContext = new HttpControllerContext(testGlobalConfiguration, routeData, request);
        controllerContext.ControllerDescriptor = controlleDescriptor;

        ApiControllerActionSelector selector = new ApiControllerActionSelector();
        HttpActionDescriptor actionDescriptor = selector.SelectAction(controllerContext);

        Assert.IsTrue(controlleDescriptor.ControllerType.Equals(typeof(IdeasController)));
        Assert.IsTrue(actionDescriptor.ActionName == "GetIdeas");
Closed Sep 16, 2013 at 8:07 PM by danroth27
See earlier comment on how to do this using an action filter.

comments

HongmeiG wrote Mar 6, 2013 at 1:13 AM

Another unit testability issue.

danroth27 wrote Sep 16, 2013 at 8:07 PM

Instead of trying to run the dispatcher pipeline yourself it's probably easier just to run the whole pipeline in memory and then decorate the request with the selected action. You can do this using an action filter, like this:
public class SelectedActionFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        actionContext.Request.Properties["selected_action"] = actionContext.ActionDescriptor;
    }
}

[TestClass]
public class ValuesControllerTest
{
    [TestMethod]
    public void ActionSelection()
    {
        HttpConfiguration config = new HttpConfiguration();
        WebApiConfig.Register(config);
        Assert.IsTrue(IsActionSelected(
            HttpMethod.Post,
            "http://localhost/api/values/",
            config,
            typeof(ValuesController),
            "Post"));
    }

    public bool IsActionSelected(HttpMethod method, string uri, HttpConfiguration config, Type controller, string actionName)
    {
        config.Filters.Add(new SelectedActionFilter());
        HttpServer server = new HttpServer(config);
        HttpClient client = new HttpClient(server);
        HttpRequestMessage request = new HttpRequestMessage(method, uri);
        HttpResponseMessage response = client.SendAsync(request).Result;
        HttpActionDescriptor actionDescriptor = (HttpActionDescriptor) response.RequestMessage.Properties["selected_action"];
        return controller == actionDescriptor.ControllerDescriptor.ControllerType && actionName == actionDescriptor.ActionName;
    }
}

danroth27 wrote Sep 16, 2013 at 9:27 PM

The filter should probably also short circuit the request by setting actionContext.Response, but you get the idea.