当我的 API 需要授权令牌时,如何使用 Asp.Net Core 2.0 的 in-memory TestServer class 进行集成测试?

How can I use Asp.Net Core 2.0's in-memory TestServer class for Integration Tests when my API requires an Authorization Token?

我正在开发 ASP.NET Core 2.0 Web API,我想使用 ASP.NET Core 的 TestServer class 进行一些集成测试。我使用 xUnit 作为我的测试框架,所以我创建了一个 TestServerFixture class 来创建 in-memory TestServer 实例,然后使用 TestServer 的 .CreateClient() 创建 HTTPClient 实例。

我的网站 API 需要来自我的 Azure AD 的 OAuth2.0 访问令牌。我在 Startup.cs、ConfigureServices 方法中使用此代码进行设置:

        // Add Azure AD OAUTH2.0 Authentication Services
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));

在我的控制器中,我在 class 上有 [Authorize] 属性。

因此,对于我的集成测试设置,我的 TestServerFixture 中有一个方法可以从 Azure AD 获取有效令牌,并将其添加到我的客户端请求 header 中,如下所示;

Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await _testServerFixture.GetAccessToken());

当我调试我的集成测试时,我可以看到请求确​​实包含一个有效的访问令牌,但是当我 运行 我的集成测试时,我仍然从 API 收到 401 Unauthorized。

在进行一些挖掘之后,我发现了一些资源,这些资源讨论了与 TestServer 类似的问题,但与身份验证相关,而不是授权,正如我所经历的那样。以下是这些资源的链接;

https://medium.com/@zbartl/authentication-and-asp-net-core-integration-testing-using-testserver-15d47b03045a

http://geeklearning.io/how-to-deal-with-identity-when-testing-an-asp-net-core-application/

这些都是关于使用自定义中间件将 ClaimsPrincipal 分配给 context.user。由于这是基于身份验证而不是授权,我不确定我是否可以为我的访问令牌做类似的事情。

我知道在我的 API 中,我可以访问 HTTPContext.User 并提取 AppId 值,它是访问令牌的一部分,所以看起来身份验证和授权都使用Context.User.

因此,在我为此花时间构建自己的自定义中间件之前,我想看看是否有人已经解决了这个问题,或者是否知道可以满足我需要的 NuGet。

编辑 - 解决方案

我展示这个是为了防止其他人 运行 陷入这个问题。

我最终构建了 Zach Bartlett 在他的 blog 中展示的中间件,但进行了以下更改。

public class AuthenticatedTestRequestMiddleware
{

    #region Class Variables

    private const string TestingAccessTokenAuthentication = "TestingAccessTokenAuthentication";
    private readonly RequestDelegate _next;

    #endregion Class Variables

    #region Constructor(s)

    public AuthenticatedTestRequestMiddleware(RequestDelegate next)
    {
        _next = next;
    }


    #endregion Constructor(s)

    public async Task Invoke(HttpContext context)
    {
        if (context.Request.Headers.Keys.Contains("X-Integration-Testing"))
        {

            if (context.Request.Headers.Keys.Contains("Authorization"))
            {
                var token = context.Request.Headers["Authorization"].First();

                var claimsIdentity = new ClaimsIdentity(new List<Claim>
                {
                    new Claim(ClaimTypes.Authentication, token)
                }, TestingAccessTokenAuthentication);

                var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
                context.User = claimsPrincipal;
            }
        }

        await _next(context);
    }

}

有一个很有趣 "Gotcha"。

Zach 的博客中有代码;

public const string TestingHeader = "X-Integration-Testing";

在他的中间件的顶部,然后在测试中引用 TestingHeader 以获取 header collection 中的密钥,就像这样;

if (context.Request.Headers.Keys.Contains(TestingHeader)  

在我将字符串文字而不是变量放入 .Contains() 子句之前,这样做对我来说是失败的。

现在,我的集成测试通过了 200 OK 响应。 :)

我找到了遵循 Zach Bartlett 的 blog post 的解决方案,并进行了一些小的更改以使其适用于身份验证 header。代码显示为我上面的原始 post 中的编辑。