如何对ASP.NET Web API路由进行单元测试?

[英]How can you unit test ASP.NET Web API routing?


I'm trying to write some unit tests to ensure that requests made to my Web API are routed to the expected API controller action with the expected arguments.

我正在尝试编写一些单元测试,以确保对我的Web API发出的请求被路由到具有预期参数的预期API控制器操作。

I've tried to create a test using the HttpServer class, but I get 500 responses from the server and no information to debug the problem.

我试图使用HttpServer类创建一个测试,但我从服务器得到500个响应,没有信息来调试问题。

Is there a way to create a unit tests for the routing of an ASP.NET Web API site?

有没有办法为ASP.NET Web API站点的路由创建单元测试?

Ideally, I'd like to create a request using HttpClient and have the server handle the request and pass it through the expected routing process.

理想情况下,我想使用HttpClient创建一个请求,让服务器处理请求并将其传递给预期的路由进程。

4 个解决方案

#1


25  

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

我写了一篇关于测试路线的博客文章,并做了很多你要问的事情:

http://www.strathweb.com/2012/08/testing-routes-in-asp-net-web-api/

http://www.strathweb.com/2012/08/testing-routes-in-asp-net-web-api/

Hope it helps.

希望能帮助到你。

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.

另外一个优点是我使用反射来提供动作方法 - 所以不是使用带有字符串的路由,而是以强类型的方式添加它们。使用这种方法,如果您的操作名称发生变化,测试将无法编译,因此您可以轻松地发现错误。

#2


7  

The best way to test your routes for your ASP.NET Web API application is the integration test your endpoints.

测试ASP.NET Web API应用程序路由的最佳方法是对端点进行集成测试。

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.

这是ASP.NET Web API应用程序的简单集成测试示例。这不是主要测试您的路线,但它无形地测试它们。此外,我在这里使用XUnit,Autofac和Moq。

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

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:

我用这个测试有一些外部助手。其中之一是NullCurrentPrincipalAttribute。由于您的测试将在您的Windows身份下运行,因此将使用此标识设置Thread.CurrentPrincipal。因此,如果您在应用程序中使用某种授权,最好首先摆脱这种情况:

public class NullCurrentPrincipalAttribute : BeforeAfterTestAttribute {

    public override void Before(MethodInfo methodUnderTest) {

        Thread.CurrentPrincipal = null;
    }
}

Then, I create a mock MembershipService. This is application specific setup. So, this will be changed for your own implementation.

然后,我创建一个模拟MembershipService。这是应用程序特定的设置。因此,这将根据您自己的实现进行更改。

The GetInitialServices creates the Autofac container for me.

GetInitialServices为我创建了Autofac容器。

private static IContainer GetInitialServices(
    IMembershipService memSrv) {

    var builder = IntegrationTestHelper
        .GetEmptyContainerBuilder();

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

    return builder.Build();
}

The GetInitialIntegrationTestConfig method is just initializes my configuration.

GetInitialIntegrationTestConfig方法只是初始化我的配置。

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

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

RouteConfig.RegisterRoutes方法基本上注册我的路由。我还有一个扩展方法来在HttpServer上创建一个HttpClient。

internal static class HttpServerExtensions {

    internal static HttpClient ToHttpClient(
        this HttpServer httpServer) {

        return new HttpClient(httpServer);
    }
}

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

最后,我有一个名为HttpRequestMessageHelper的静态类,它有一堆静态方法来构造一个新的HttpRequestMessage实例。

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

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

我在我的应用程序中使用基本身份验证。因此,这个类有一些方法可以使用Authentication头构造一个HttpRequestMessege。

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.

最后,我做了我的Act和Assert来验证我需要的东西。这可能是一个过度的样本,但我认为这会给你一个好主意。

Here is a great blog post on Integration Testing with HttpServer. Also, here is another great post on Testing routes in ASP.NET Web API.

这是一篇关于使用HttpServer进行集成测试的精彩博客文章。另外,这是另一篇关于ASP.NET Web API中测试路由的精彩文章。

#3


6  

For testing ASP.NET Web API routes, you can use a third party tool like MvcRouteTester or MyWebApi. Here is a sample code to get you started:

要测试ASP.NET Web API路由,可以使用第三方工具,如MvcRouteTester或MyWebApi。以下是一个示例代码,可帮助您入门:

MyWebApi
    .Routes()
    .ShouldMap("api/WebApiController/SomeAction")
    .WithHttpMethod(HttpMethod.Post)
    .WithJsonContent(@"{""SomeInt"": 1, ""SomeString"": ""Test""}")
    .To<WebApiController>(c => c.SomeAction(new RequestModel
    {
        SomeInt = 1,
        SomeString = "Test"
    }));

If you want to implement the testing by yourself, you can take a look how to make the internal Web API route resolver map the request for you. Then you only need to compare the selected controller and actions. LINK HERE.

如果您想自己实施测试,可以查看如何使内部Web API路由解析器为您映射请求。然后,您只需要比较选定的控制器和操作。链接在这里。

#4


1  

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.

嗨,当您要测试您的路线时,主要目标是使用此测试测试GetRouteData(),确保路线系统正确识别您的请求并选择正确的路线。

[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"]);
    }
}

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.

这很重要但是还不够,甚至检查选择了正确的路由并且提取了正确的路由数据并不能确保选择正确的控制器和操作如果不重写默认的IHttpActionSelector和IHttpControllerSelector服务,这是一个方便的方法与你自己的。

 [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);
        }
    }
智能推荐

注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:http://www.silva-art.net/blog/2012/10/15/4b20f6e0f8b484e4ff020ff437668748.html



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告