C# 如何对 ASP.NET Web API 路由进行单元测试?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12891229/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can you unit test ASP.NET Web API routing?
提问by Paul Turner
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 HttpServerclass, 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 HttpClientand have the server handle the request and pass it through the expected routing process.
理想情况下,我想创建一个请求HttpClient并让服务器处理该请求并通过预期的路由过程传递它。
采纳答案by Filip W
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.
额外的好处是我使用反射来提供操作方法 - 所以不是使用带有字符串的路由,而是以强类型的方式添加它们。使用这种方法,如果您的操作名称发生更改,测试将无法编译,因此您将很容易发现错误。
回答by tugberk
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.CurrentPrincipalwill 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 GetInitialServicescreates 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 GetInitialIntegrationTestConfigmethod 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.RegisterRoutesmethod basically registers my routes. I also have a little extension method to create an HttpClientover the HttpServer.
该RouteConfig.RegisterRoutes方法基本上注册了我的路线。我也有一点点扩展方法来创建一个HttpClient在HttpServer。
internal static class HttpServerExtensions {
internal static HttpClient ToHttpClient(
this HttpServer httpServer) {
return new HttpClient(httpServer);
}
}
Finally, I have a static class called HttpRequestMessageHelperwhich has bunch of static methods to construct a new HttpRequestMessageinstance.
最后,我有一个静态类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 HttpRequestMessegewith the Authentication header.
我在我的应用程序中使用基本身份验证。所以,这个类有一些方法可以HttpRequestMessege用 Authentication 标头构造一个。
At the end, I do my Actand Assertto 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 中测试路由的好文章。
回答by Alejandro Serret
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 IHttpActionSelectorand IHttpControllerSelectorservices 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);
}
}

