如何对ASP.NET Web API路由进行unit testing?

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

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

有没有办法为ASP.NET Web API站点的路由创建unit testing?

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

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

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

希望能帮助到你。

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

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

这是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(k => k == key1 || k == key2 || k == key3 || k == key4 ) ) ).Returns(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(); // Assert Assert.Equal(key2, role.Key); Assert.Equal("FooBar", role.Name); } } 

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

 public class NullCurrentPrincipalAttribute : BeforeAfterTestAttribute { public override void Before(MethodInfo methodUnderTest) { Thread.CurrentPrincipal = null; } } 

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

GetInitialServices为我创建了Autofac容器。

 private static IContainer GetInitialServices( IMembershipService memSrv) { var builder = IntegrationTestHelper .GetEmptyContainerBuilder(); builder.Register(c => memSrv) .As() .InstancePerApiRequest(); return builder.Build(); } 

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

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

 internal static class HttpServerExtensions { internal static HttpClient ToHttpClient( this HttpServer httpServer) { return new HttpClient(httpServer); } } 

最后,我有一个名为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 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 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 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); } } 

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

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

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

嗨,当您要测试您的路线时,主要目标是使用此测试测试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"]); } } 

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

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