Question

I am trying to set up some route tests using the WebApiContrib.Testing library. My get tests (like this) work fine...

    [Test]
    [Category("Auth Api Tests")]
    public void TheAuthControllerAcceptsASingleItemGetRouteWithAHashString()
    {
        "~/auth/sjkfhiuehfkshjksdfh".ShouldMapTo<AuthController>(c => c.Get("sjkfhiuehfkshjksdfh"));
    }

I am rather lost on the post test - I currently have the following which fails with a NotImplementedException...

    [Test]
    [Category("Auth Api Tests")]
    public void TheAuthControllerAcceptsAPost()
    {
        "~/auth".ShouldMapTo<AuthController>(c => c.Post(new AuthenticationCredentialsModel()), "POST");
    }

Here's the setup and teardown for completeness...

    [SetUp]
    public void SetUpTest()
    {
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        WebApiConfig.Register(GlobalConfiguration.Configuration);
    }

    [TearDown]
    public void TearDownTest()
    {
        RouteTable.Routes.Clear();
        GlobalConfiguration.Configuration.Routes.Clear();
    }

The route I am trying to test is the default POST route, which maps to this method call...

    [AllowAnonymous]
    public HttpResponseMessage Post([FromBody] AuthenticationCredentialsModel model)
    { *** Some code here that doesn't really matter *** }

I am also getting a failure on this test that tests the standard GET route without parameters returns all of the items...

    [Test]
    [Category("VersionInfo Api Tests")]
    public void TheVersionInfoControllerAcceptsAMultipleItemGetRouteForAllItems()
    {
        "~/versioninfo".ShouldMapTo<VersionInfoController>(c => c.Get());
    }

Which is testing this method...

    public HttpResponseMessage Get()
    { *** Some code here that doesn't really matter *** }

This library was recommended by several articles I read, but I'm not sure now if I'm doing something wrong or if it's just quite limited and I'm better off rolling my own.

Was it helpful?

Solution 2

I fixed this in the end by writing my own, after reading a post by whyleee on another question here - Testing route configuration in ASP.NET WebApi (WebApiContrib.Testing didn't seem to work for me)

I merged his post with some of the elements I liked syntactically from the WebApiContrib.Testing library to generate the following helper class.

This allows me to write really lightweight tests like this...

[Test]
[Category("Auth Api Tests")]
public void TheAuthControllerAcceptsASingleItemGetRouteWithAHashString()
{
    "http://api.siansplan.com/auth/sjkfhiuehfkshjksdfh".ShouldMapTo<AuthController>("Get", "hash");
}

[Test]
[Category("Auth Api Tests")]
public void TheAuthControllerAcceptsAPost()
{
    "http://api.siansplan.com/auth".ShouldMapTo<AuthController>("Post", HttpMethod.Post);
}

The helper class looks like this...

using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;
using System.Web.Http.Hosting;
using System.Web.Http.Routing;

namespace SiansPlan.Api.Tests.Helpers
{
    public static class RoutingTestHelper
    {
        /// <summary>
        /// Routes the request.
        /// </summary>
        /// <param name="config">The config.</param>
        /// <param name="request">The request.</param>
        /// <returns>Inbformation about the route.</returns>
        public static RouteInfo RouteRequest(HttpConfiguration config, HttpRequestMessage request)
        {
            // create context
            var controllerContext = new HttpControllerContext(config, new Mock<IHttpRouteData>().Object, request);

            // get route data
            var routeData = config.Routes.GetRouteData(request);
            RemoveOptionalRoutingParameters(routeData.Values);

            request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
            controllerContext.RouteData = routeData;

            // get controller type
            var controllerDescriptor = new DefaultHttpControllerSelector(config).SelectController(request);
            controllerContext.ControllerDescriptor = controllerDescriptor;

            // get action name
            var actionMapping = new ApiControllerActionSelector().SelectAction(controllerContext);

            var info = new RouteInfo(controllerDescriptor.ControllerType, actionMapping.ActionName);

            foreach (var param in actionMapping.GetParameters())
            {
                info.Parameters.Add(param.ParameterName);
            }

            return info;
        }

        #region | Extensions |

        /// <summary>
        /// Determines that a URL maps to a specified controller.
        /// </summary>
        /// <typeparam name="TController">The type of the controller.</typeparam>
        /// <param name="fullDummyUrl">The full dummy URL.</param>
        /// <param name="action">The action.</param>
        /// <param name="parameterNames">The parameter names.</param>
        /// <returns></returns>
        public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, params string[] parameterNames)
        {
            return ShouldMapTo<TController>(fullDummyUrl, action, HttpMethod.Get, parameterNames);
        }

        /// <summary>
        /// Determines that a URL maps to a specified controller.
        /// </summary>
        /// <typeparam name="TController">The type of the controller.</typeparam>
        /// <param name="fullDummyUrl">The full dummy URL.</param>
        /// <param name="action">The action.</param>
        /// <param name="httpMethod">The HTTP method.</param>
        /// <param name="parameterNames">The parameter names.</param>
        /// <returns></returns>
        /// <exception cref="System.Exception"></exception>
        public static bool ShouldMapTo<TController>(this string fullDummyUrl, string action, HttpMethod httpMethod, params string[] parameterNames)
        {
            var request = new HttpRequestMessage(httpMethod, fullDummyUrl);
            var config = new HttpConfiguration();
            WebApiConfig.Register(config);

            var route = RouteRequest(config, request);

            var controllerName = typeof(TController).Name;
            if (route.Controller.Name != controllerName)
                throw new Exception(String.Format("The specified route '{0}' does not match the expected controller '{1}'", fullDummyUrl, controllerName));

            if (route.Action.ToLowerInvariant() != action.ToLowerInvariant())
                throw new Exception(String.Format("The specified route '{0}' does not match the expected action '{1}'", fullDummyUrl, action));

            if (parameterNames.Any())
            {
                if (route.Parameters.Count != parameterNames.Count())
                    throw new Exception(
                        String.Format(
                            "The specified route '{0}' does not have the expected number of parameters - expected '{1}' but was '{2}'",
                            fullDummyUrl, parameterNames.Count(), route.Parameters.Count));

                foreach (var param in parameterNames)
                {
                    if (!route.Parameters.Contains(param))
                        throw new Exception(
                            String.Format("The specified route '{0}' does not contain the expected parameter '{1}'",
                                          fullDummyUrl, param));
                }
            }

            return true;
        }

        #endregion

        #region | Private Methods |

        /// <summary>
        /// Removes the optional routing parameters.
        /// </summary>
        /// <param name="routeValues">The route values.</param>
        private static void RemoveOptionalRoutingParameters(IDictionary<string, object> routeValues)
        {
            var optionalParams = routeValues
                .Where(x => x.Value == RouteParameter.Optional)
                .Select(x => x.Key)
                .ToList();

            foreach (var key in optionalParams)
            {
                routeValues.Remove(key);
            }
        }

        #endregion
    }

    /// <summary>
    /// Route information
    /// </summary>
    public class RouteInfo
    {
        #region | Construction |

        /// <summary>
        /// Initializes a new instance of the <see cref="RouteInfo"/> class.
        /// </summary>
        /// <param name="controller">The controller.</param>
        /// <param name="action">The action.</param>
        public RouteInfo(Type controller, string action)
        {
            Controller = controller;
            Action = action;
            Parameters = new List<string>();
        }

        #endregion

        public Type Controller { get; private set; }
        public string Action { get; private set; }
        public List<string> Parameters { get; private set; }
    }
}

OTHER TIPS

There seems to be some nasty issues here - after debugging through the WebApiContrib.Testing library, I've found the following...

In the route mapping code used for the libraries own tests, the route mappings look like this...

        // GET /api/{resource}
        routes.MapHttpRoute(
            name: "Web API Get All",
            routeTemplate: "api/{controller}",
            defaults: new {action = "Get"},
            constraints: new {httpMethod = new HttpMethodConstraint("GET")}
            );

My route mapping looks like this for essentially the same route...

    config.Routes.MapHttpRoute("DefaultApiGet", "{controller}", 
        new { action = "Get" }, 
        new { httpMethod = new HttpMethodConstraint(HttpMethod.Get) });

Notice the different syntax for constraint building. My route mapping code won't allow strings. Maybe this is something that's changed in the WebAPI version since the library was written, I'm not sure, but it seems that any route that I'm trying to test with an HttpMethodConstraint will fail.

I'm continuing to investigate this one; I now have a 'why', but not a 'how to fix' yet.

UPDATE: The WebAPI routing takes two forms of constraint, one of which is just a string (more details here - http://forums.asp.net/p/1777747/4904556.aspx/1?Re+System+Web+Http+Routing+IHttpRouteConstraint+vs+System+Web+Routing+IRouteConstraint). I tried changing the routing table to see if that would make any difference, but it didn't - just to let you know!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top