Rectangle 27 0

c How can you unit test ASP.NET Web API routing?


GetInitialIntegrationTestConfig
MembershipService
[Fact, NullCurrentPrincipal]
public async Task 
    Returns_200_And_Role_With_Key() {

    // Arrange
    Guid key1 = Guid.NewGuid(),
         key2 = Guid.NewGuid(),
         key3 = Guid.NewGuid(),
         key4 = Guid.NewGuid();

    var mockMemSrv = ServicesMockHelper
        .GetInitialMembershipService();

    mockMemSrv.Setup(ms => ms.GetRole(
            It.Is<Guid>(k =>
                k == key1 || k == key2 || 
                k == key3 || k == key4
            )
        )
    ).Returns<Guid>(key => new Role { 
        Key = key, Name = "FooBar"
    });

    var config = IntegrationTestHelper
        .GetInitialIntegrationTestConfig(GetInitialServices(mockMemSrv.Object));

    using (var httpServer = new HttpServer(config))
    using (var client = httpServer.ToHttpClient()) {

        var request = HttpRequestMessageHelper
            .ConstructRequest(
                httpMethod: HttpMethod.Get,
                uri: string.Format(
                    "https://localhost/{0}/{1}", 
                    "api/roles", 
                    key2.ToString()),
                mediaType: "application/json",
                username: Constants.ValidAdminUserName,
                password: Constants.ValidAdminPassword);

        // Act
        var response = await client.SendAsync(request);
        var role = await response.Content.ReadAsAsync<RoleDto>();

        // Assert
        Assert.Equal(key2, role.Key);
        Assert.Equal("FooBar", role.Name);
    }
}
internal static class HttpRequestMessageHelper {

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri) {

        return new HttpRequestMessage(httpMethod, uri);
    }

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri, string mediaType) {

        return ConstructRequest(
            httpMethod, 
            uri, 
            new MediaTypeWithQualityHeaderValue(mediaType));
    }

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri,
        IEnumerable<string> mediaTypes) {

        return ConstructRequest(
            httpMethod,
            uri,
            mediaTypes.ToMediaTypeWithQualityHeaderValues());
    }

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri, string mediaType, 
        string username, string password) {

        return ConstructRequest(
            httpMethod, uri, new[] { mediaType }, username, password);
    }

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri, 
        IEnumerable<string> mediaTypes,
        string username, string password) {

        var request = ConstructRequest(httpMethod, uri, mediaTypes);
        request.Headers.Authorization = new AuthenticationHeaderValue(
            "Basic",
            EncodeToBase64(
                string.Format("{0}:{1}", username, password)));

        return request;
    }

    // Private helpers
    private static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri,
        MediaTypeWithQualityHeaderValue mediaType) {

        return ConstructRequest(
            httpMethod, 
            uri, 
            new[] { mediaType });
    }

    private static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri,
        IEnumerable<MediaTypeWithQualityHeaderValue> mediaTypes) {

        var request = ConstructRequest(httpMethod, uri);
        request.Headers.Accept.AddTo(mediaTypes);

        return request;
    }

    private static string EncodeToBase64(string value) {

        byte[] toEncodeAsBytes = Encoding.UTF8.GetBytes(value);
        return Convert.ToBase64String(toEncodeAsBytes);
    }
}
internal static class HttpServerExtensions {

    internal static HttpClient ToHttpClient(
        this HttpServer httpServer) {

        return new HttpClient(httpServer);
    }
}
internal static class IntegrationTestHelper {

    internal static HttpConfiguration GetInitialIntegrationTestConfig() {

        var config = new HttpConfiguration();
        RouteConfig.RegisterRoutes(config.Routes);
        WebAPIConfig.Configure(config);

        return config;
    }

    internal static HttpConfiguration GetInitialIntegrationTestConfig(IContainer container) {

        var config = GetInitialIntegrationTestConfig();
        AutofacWebAPI.Initialize(config, container);

        return config;
    }
}
private static IContainer GetInitialServices(
    IMembershipService memSrv) {

    var builder = IntegrationTestHelper
        .GetEmptyContainerBuilder();

    builder.Register(c => memSrv)
        .As<IMembershipService>()
        .InstancePerApiRequest();

    return builder.Build();
}
public class NullCurrentPrincipalAttribute : BeforeAfterTestAttribute {

    public override void Before(MethodInfo methodUnderTest) {

        Thread.CurrentPrincipal = null;
    }
}

At the end, I do my Act and Assert to verify the things I need. This may be an overkill sample but I think this will give you a great idea.

Finally, I have a static class called HttpRequestMessageHelper which has bunch of static methods to construct a new HttpRequestMessage instance.

Here is simple Integration test sample for your ASP.NET Web API application. This doesn't mainly test your routes but it invisibly tests them. Also, I am using XUnit, Autofac and Moq here.

I am using Basic Authentication in my application. So, this class has some methods which construct an HttpRequestMessege with the Authentication header.

Informative answer, but why is this better than unit testing your routes?

The GetInitialServices creates the Autofac container for me.

The RouteConfig.RegisterRoutes method basically registers my routes. I also have a little extension method to create an HttpClient over the HttpServer.

There are a few external helpers I use for this test. The one of them is the NullCurrentPrincipalAttribute. As your test will run under your Windows Identity, the Thread.CurrentPrincipal will be set with this identity. So, if you are using some sort of authorization in your application, it is best to get rid of this in the first place:

Note
Rectangle 27 0

c How can you unit test ASP.NET Web API routing?


@FilipW Great article - could you maybe post the examples here also? ya know incase you won't be able to keep your own blog post ;)

Additional advantage is that I used reflection to provide action methods - so instead of using routes with strings, you do add them in a strongly typed manner. With this approach, if your action names ever change, the tests won't compile so you will easily be able to spot errors.

I have written a blog post about testing routes and doing pretty much what you are asking about:

It would be useful if there was some code provided, supplying a link that is no longer active is hardly a useful accepted answer.

It's adavisable to bring at least the bare minimum of an external article (a not SO article) just in case the external article dissapears. Could you update your answer with a little bit of info?

Link works, I'm not sure what is your point..

Sorry, I clicked the link on multiple computers and my phone and I was not getting a page returned. I have confirmed that I can get there now. Hopefully it answers my question. Thanks.

Note
Rectangle 27 0

c How can you unit test ASP.NET Web API routing?


GetInitialIntegrationTestConfig
MembershipService
[Fact, NullCurrentPrincipal]
public async Task 
    Returns_200_And_Role_With_Key() {

    // Arrange
    Guid key1 = Guid.NewGuid(),
         key2 = Guid.NewGuid(),
         key3 = Guid.NewGuid(),
         key4 = Guid.NewGuid();

    var mockMemSrv = ServicesMockHelper
        .GetInitialMembershipService();

    mockMemSrv.Setup(ms => ms.GetRole(
            It.Is<Guid>(k =>
                k == key1 || k == key2 || 
                k == key3 || k == key4
            )
        )
    ).Returns<Guid>(key => new Role { 
        Key = key, Name = "FooBar"
    });

    var config = IntegrationTestHelper
        .GetInitialIntegrationTestConfig(GetInitialServices(mockMemSrv.Object));

    using (var httpServer = new HttpServer(config))
    using (var client = httpServer.ToHttpClient()) {

        var request = HttpRequestMessageHelper
            .ConstructRequest(
                httpMethod: HttpMethod.Get,
                uri: string.Format(
                    "https://localhost/{0}/{1}", 
                    "api/roles", 
                    key2.ToString()),
                mediaType: "application/json",
                username: Constants.ValidAdminUserName,
                password: Constants.ValidAdminPassword);

        // Act
        var response = await client.SendAsync(request);
        var role = await response.Content.ReadAsAsync<RoleDto>();

        // Assert
        Assert.Equal(key2, role.Key);
        Assert.Equal("FooBar", role.Name);
    }
}
internal static class HttpRequestMessageHelper {

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri) {

        return new HttpRequestMessage(httpMethod, uri);
    }

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri, string mediaType) {

        return ConstructRequest(
            httpMethod, 
            uri, 
            new MediaTypeWithQualityHeaderValue(mediaType));
    }

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri,
        IEnumerable<string> mediaTypes) {

        return ConstructRequest(
            httpMethod,
            uri,
            mediaTypes.ToMediaTypeWithQualityHeaderValues());
    }

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri, string mediaType, 
        string username, string password) {

        return ConstructRequest(
            httpMethod, uri, new[] { mediaType }, username, password);
    }

    internal static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri, 
        IEnumerable<string> mediaTypes,
        string username, string password) {

        var request = ConstructRequest(httpMethod, uri, mediaTypes);
        request.Headers.Authorization = new AuthenticationHeaderValue(
            "Basic",
            EncodeToBase64(
                string.Format("{0}:{1}", username, password)));

        return request;
    }

    // Private helpers
    private static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri,
        MediaTypeWithQualityHeaderValue mediaType) {

        return ConstructRequest(
            httpMethod, 
            uri, 
            new[] { mediaType });
    }

    private static HttpRequestMessage ConstructRequest(
        HttpMethod httpMethod, string uri,
        IEnumerable<MediaTypeWithQualityHeaderValue> mediaTypes) {

        var request = ConstructRequest(httpMethod, uri);
        request.Headers.Accept.AddTo(mediaTypes);

        return request;
    }

    private static string EncodeToBase64(string value) {

        byte[] toEncodeAsBytes = Encoding.UTF8.GetBytes(value);
        return Convert.ToBase64String(toEncodeAsBytes);
    }
}
internal static class HttpServerExtensions {

    internal static HttpClient ToHttpClient(
        this HttpServer httpServer) {

        return new HttpClient(httpServer);
    }
}
internal static class IntegrationTestHelper {

    internal static HttpConfiguration GetInitialIntegrationTestConfig() {

        var config = new HttpConfiguration();
        RouteConfig.RegisterRoutes(config.Routes);
        WebAPIConfig.Configure(config);

        return config;
    }

    internal static HttpConfiguration GetInitialIntegrationTestConfig(IContainer container) {

        var config = GetInitialIntegrationTestConfig();
        AutofacWebAPI.Initialize(config, container);

        return config;
    }
}
private static IContainer GetInitialServices(
    IMembershipService memSrv) {

    var builder = IntegrationTestHelper
        .GetEmptyContainerBuilder();

    builder.Register(c => memSrv)
        .As<IMembershipService>()
        .InstancePerApiRequest();

    return builder.Build();
}
public class NullCurrentPrincipalAttribute : BeforeAfterTestAttribute {

    public override void Before(MethodInfo methodUnderTest) {

        Thread.CurrentPrincipal = null;
    }
}

At the end, I do my Act and Assert to verify the things I need. This may be an overkill sample but I think this will give you a great idea.

Finally, I have a static class called HttpRequestMessageHelper which has bunch of static methods to construct a new HttpRequestMessage instance.

Here is simple Integration test sample for your ASP.NET Web API application. This doesn't mainly test your routes but it invisibly tests them. Also, I am using XUnit, Autofac and Moq here.

I am using Basic Authentication in my application. So, this class has some methods which construct an HttpRequestMessege with the Authentication header.

Informative answer, but why is this better than unit testing your routes?

The GetInitialServices creates the Autofac container for me.

The RouteConfig.RegisterRoutes method basically registers my routes. I also have a little extension method to create an HttpClient over the HttpServer.

There are a few external helpers I use for this test. The one of them is the NullCurrentPrincipalAttribute. As your test will run under your Windows Identity, the Thread.CurrentPrincipal will be set with this identity. So, if you are using some sort of authorization in your application, it is best to get rid of this in the first place:

Note
Rectangle 27 0

c How can you unit test ASP.NET Web API routing?


@FilipW Great article - could you maybe post the examples here also? ya know incase you won't be able to keep your own blog post ;)

Additional advantage is that I used reflection to provide action methods - so instead of using routes with strings, you do add them in a strongly typed manner. With this approach, if your action names ever change, the tests won't compile so you will easily be able to spot errors.

I have written a blog post about testing routes and doing pretty much what you are asking about:

It would be useful if there was some code provided, supplying a link that is no longer active is hardly a useful accepted answer.

It's adavisable to bring at least the bare minimum of an external article (a not SO article) just in case the external article dissapears. Could you update your answer with a little bit of info?

Link works, I'm not sure what is your point..

Sorry, I clicked the link on multiple computers and my phone and I was not getting a page returned. I have confirmed that I can get there now. Hopefully it answers my question. Thanks.

Note
Rectangle 27 0

c How can you unit test ASP.NET Web API routing?


[Theory]
        [InlineData("http://localhost:12345/api/Cars/123", "GET", typeof(CarsController), "GetCars")]
        [InlineData("http://localhost:12345/api/Cars", "GET", typeof(CarsController), "GetCars")]
        public void Ensure_Correct_Controller_and_Action_Selected(string url,string method,
                                                    Type controllerType,string actionName) {
            //Arrange
            var config = new HttpConfiguration();
            WebApiConfig.Register(config);

            var controllerSelector = config.Services.GetHttpControllerSelector();
            var actionSelector = config.Services.GetActionSelector();

            var request = new HttpRequestMessage(new HttpMethod(method),url);

            config.EnsureInitialized();

            var routeData = config.Routes.GetRouteData(request);
            request.Properties[HttpPropertyKeys.HttpRouteDataKey] = routeData;
            request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
            //Act
            var ctrlDescriptor = controllerSelector.SelectController(request);
            var ctrlContext = new HttpControllerContext(config, routeData, request)
            {
                ControllerDescriptor = ctrlDescriptor
            };
            var actionDescriptor = actionSelector.SelectAction(ctrlContext);
            //Assert
            Assert.NotNull(ctrlDescriptor);
            Assert.Equal(controllerType, ctrlDescriptor.ControllerType);
            Assert.Equal(actionName, actionDescriptor.ActionName);
        }
    }
[Theory]
[InlineData("http://localhost:5240/foo/route", "GET", false, null, null)]
[InlineData("http://localhost:5240/api/Cars/", "GET", true, "Cars", null)]
[InlineData("http://localhost:5240/api/Cars/123", "GET", true, "Cars", "123")]
public void DefaultRoute_Returns_Correct_RouteData(
     string url, string method, bool shouldfound, string controller, string id)
{
    //Arrange
    var config = new HttpConfiguration();

    WebApiConfig.Register(config);

    var actionSelector = config.Services.GetActionSelector();
    var controllerSelector = config.Services.GetHttpControllerSelector();

    var request = new HttpRequestMessage(new HttpMethod(method), url);
    config.EnsureInitialized();
    //Act
    var routeData = config.Routes.GetRouteData(request);
    //Assert
    // assert
    Assert.Equal(shouldfound, routeData != null);
    if (shouldfound)
    {
        Assert.Equal(controller, routeData.Values["controller"]);
        Assert.Equal(id == null ? (object)RouteParameter.Optional : (object)id, routeData.
        Values["id"]);
    }
}

hi when you going to Test your Routes the main objective is test GetRouteData() with this test you ensure that the route system recognize correctly your request and the correct route is select.

this is important but is'not enough, even checking that the correct route is selected and the correct route data are extracted does not ensure that the correct controller and action are selected this is a handy method if you not rewrite the default IHttpActionSelector and IHttpControllerSelector services with your own.

Note