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

Pau*_*ner 30 c# unit-testing asp.net-web-api

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

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

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

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

Fil*_*p W 26

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

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

希望能帮助到你.

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

  • 如果提供了一些代码,提供不再有效的链接几乎不是一个有用的接受答案将是有用的. (7认同)
  • 如果外部文章消失,最好至少带一个外部文章(不是SO文章).你能用一点信息更新你的答案吗? (7认同)

tug*_*erk 9

测试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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public class NullCurrentPrincipalAttribute : BeforeAfterTestAttribute {

    public override void Before(MethodInfo methodUnderTest) {

        Thread.CurrentPrincipal = null;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

GetInitialServices我创建Autofac容器.

private static IContainer GetInitialServices(
    IMembershipService memSrv) {

    var builder = IntegrationTestHelper
        .GetEmptyContainerBuilder();

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

    return builder.Build();
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

RouteConfig.RegisterRoutes方法基本上记录了我的路线.我也有一点点扩展方法来创建一个HttpClientHttpServer.

internal static class HttpServerExtensions {

    internal static HttpClient ToHttpClient(
        this HttpServer httpServer) {

        return new HttpClient(httpServer);
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,我有一个静态类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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

最后,我做了我的ActAssert来验证我需要的东西.这可能是一个矫枉过正的样本,但我认为这会给你一个好主意.

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

  • 信息性答案,但为什么这比单元测试您的路线更好? (6认同)