我正在尝试编写一些单元测试,以确保对我的 Web API 发出的请求被路由到具有预期参数的预期 API 控制器操作。
我尝试使用HttpServer
该类创建测试,但我从服务器收到 500 条响应,但没有任何信息可以调试问题。
有没有办法为 ASP.NET Web API 站点的路由创建单元测试?
理想情况下,我想使用创建一个请求HttpClient
并让服务器处理该请求并将其传递给预期的路由过程。
我正在尝试编写一些单元测试,以确保对我的 Web API 发出的请求被路由到具有预期参数的预期 API 控制器操作。
我尝试使用HttpServer
该类创建测试,但我从服务器收到 500 条响应,但没有任何信息可以调试问题。
有没有办法为 ASP.NET Web API 站点的路由创建单元测试?
理想情况下,我想使用创建一个请求HttpClient
并让服务器处理该请求并将其传递给预期的路由过程。
我写了一篇关于测试路线的博客文章,并做了你所问的几乎所有事情:
http://www.strathweb.com/2012/08/testing-routes-in-asp-net-web-api/
希望能帮助到你。
另一个优点是我使用反射来提供操作方法 - 所以不是使用带有字符串的路由,而是以强类型的方式添加它们。使用这种方法,如果您的操作名称发生更改,测试将无法编译,因此您将能够轻松发现错误。
为您的 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<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);
}
}
我在这个测试中使用了一些外部助手。其中之一是NullCurrentPrincipalAttribute
. 由于您的测试将在您的 Windows 身份下运行,因此Thread.CurrentPrincipal
将使用此身份进行设置。因此,如果您在应用程序中使用某种授权,最好首先摆脱它:
public class NullCurrentPrincipalAttribute : BeforeAfterTestAttribute {
public override void Before(MethodInfo methodUnderTest) {
Thread.CurrentPrincipal = null;
}
}
然后,我创建了一个 mock MembershipService
。这是特定于应用程序的设置。因此,这将针对您自己的实现进行更改。
为我GetInitialServices
创建了 Autofac 容器。
private static IContainer GetInitialServices(
IMembershipService memSrv) {
var builder = IntegrationTestHelper
.GetEmptyContainerBuilder();
builder.Register(c => memSrv)
.As<IMembershipService>()
.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
方法基本上注册了我的路线。我也有一些扩展方法来创建一个HttpClient
超过HttpServer
.
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<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);
}
}
我在我的应用程序中使用基本身份验证。所以,这个类有一些方法可以HttpRequestMessege
用 Authentication 头构造一个。
最后,我执行我的Act和Assert来验证我需要的东西。这可能是一个矫枉过正的样本,但我认为这会给你一个好主意。
这是一篇关于使用 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"]);
}
}
这很重要,但还不够,即使检查是否选择了正确的路由并提取了正确的路由数据也不能确保选择了正确的控制器和操作,如果您不重写默认的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);
}
}