使用 WebApplicationFactory 时如何使用 JWT 承载方案

Pau*_*ito 6 c# xunit asp.net-web-api .net-core asp.net-core

我有一个工作的 Web API,最近更新为使用 JWT 身份验证,虽然它在我正常运行时工作,但我似乎无法让我的集成测试工作。

我想开始为我的集成测试集成令牌生成选项,但我什至无法让他们抛出 401。

当我在项目中运行任何无需 JWT 即可工作的现有集成测试时,我预计会收到 401,因为我没有任何身份验证信息,但实际上收到了错误System.InvalidOperationException : Scheme already exists: Bearer

我认为发生这种情况是因为WebApplicationFactory运行其ConfigureWebHost方法的工作方式在 Startup 类的ConfigureServices方法之后运行,当我在 jwt 服务上放置断点时,它确实会被击中两次,但考虑到这是如何WebApplicationFactory构建的,我不确定这里推荐的选项是什么。值得注意的是,即使我删除其中一项服务,我仍然收到错误:

var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(JwtBearerHandler));
services.Remove(serviceDescriptor);
Run Code Online (Sandbox Code Playgroud)

我的WebApplicationFactory基于 eshopwebapi 工厂:


    public class CustomWebApplicationFactory : WebApplicationFactory<StartupTesting>
    {
        // checkpoint for respawning to clear the database when spinning up each time
        private static Checkpoint checkpoint = new Checkpoint
        {
            
        };

        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.UseEnvironment("Testing");

            builder.ConfigureServices(async services =>
            {
                // Create a new service provider.
                var provider = services
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();

                // Add a database context (LabDbContext) using an in-memory 
                // database for testing.
                services.AddDbContext<LabDbContext>(options =>
                {
                    options.UseInMemoryDatabase("InMemoryDbForTesting");
                    options.UseInternalServiceProvider(provider);
                });

                // Build the service provider.
                var sp = services.BuildServiceProvider();

                // Create a scope to obtain a reference to the database
                // context (ApplicationDbContext).
                using (var scope = sp.CreateScope())
                {
                    var scopedServices = scope.ServiceProvider;
                    var db = scopedServices.GetRequiredService<LabDbContext>();

                    // Ensure the database is created.
                    db.Database.EnsureCreated();

                    try
                    {
                        await checkpoint.Reset(db.Database.GetDbConnection());
                    }
                    catch
                    {
                    }
                }
            }).UseStartup<StartupTesting>();
        }

        public HttpClient GetAnonymousClient()
        {
            return CreateClient();
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是我的服务注册:

    public static class ServiceRegistration
    {
        public static void AddIdentityInfrastructure(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.Authority = configuration["JwtSettings:Authority"];
                    options.Audience = configuration["JwtSettings:Audience"];
                });
            
            services.AddAuthorization(options =>
            {
                options.AddPolicy("CanReadPatients", 
                    policy => policy.RequireClaim("scope", "patients.read"));
                options.AddPolicy("CanAddPatients", 
                    policy => policy.RequireClaim("scope", "patients.add"));
                options.AddPolicy("CanDeletePatients", 
                    policy => policy.RequireClaim("scope", "patients.delete"));
                options.AddPolicy("CanUpdatePatients", 
                    policy => policy.RequireClaim("scope", "patients.update"));
            });
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是我的集成测试(我预计当前会抛出 401):

public class GetPatientIntegrationTests : IClassFixture<CustomWebApplicationFactory>
    { 
        private readonly CustomWebApplicationFactory _factory;

        public GetPatientIntegrationTests(CustomWebApplicationFactory factory)
        {
            _factory = factory;
        }

        
        [Fact]
        public async Task GetPatients_ReturnsSuccessCodeAndResourceWithAccurateFields()
        {
            var fakePatientOne = new FakePatient { }.Generate();
            var fakePatientTwo = new FakePatient { }.Generate();

            var appFactory = _factory;
            using (var scope = appFactory.Services.CreateScope())
            {
                var context = scope.ServiceProvider.GetRequiredService<LabDbContext>();
                context.Database.EnsureCreated();

                context.Patients.AddRange(fakePatientOne, fakePatientTwo);
                context.SaveChanges();
            }

            var client = appFactory.CreateClient(new WebApplicationFactoryClientOptions
            {
                AllowAutoRedirect = false
            });

            var result = await client.GetAsync("api/Patients")
                .ConfigureAwait(false);
            var responseContent = await result.Content.ReadAsStringAsync()
                .ConfigureAwait(false);
            var response = JsonConvert.DeserializeObject<Response<IEnumerable<PatientDto>>>(responseContent).Data;

            // Assert
            result.StatusCode.Should().Be(200);
            response.Should().ContainEquivalentOf(fakePatientOne, options =>
                options.ExcludingMissingMembers());
            response.Should().ContainEquivalentOf(fakePatientTwo, options =>
                options.ExcludingMissingMembers());
        }
    } 
Run Code Online (Sandbox Code Playgroud)

Wer*_*ger 8

嘿,当我寻找相同的答案时,我看到了你的帖子。我通过将以下代码放入 WebApplicationFactory 的 ConfigureWebHost 方法中解决了这个问题:

    protected override void ConfigureWebHost(
        IWebHostBuilder builder)
    {
        builder.ConfigureServices(serviceCollection =>
        {

        });
   
        // Overwrite registrations from Startup.cs
        builder.ConfigureTestServices(serviceCollection =>
        {
            var authenticationBuilder = serviceCollection.AddAuthentication();
            authenticationBuilder.Services.Configure<AuthenticationOptions>(o =>
            {
                o.SchemeMap.Clear();
                ((IList<AuthenticationSchemeBuilder>) o.Schemes).Clear();
            });
        });
    }
Run Code Online (Sandbox Code Playgroud)

我知道我迟到了四个月,但我希望你仍然有用。

  • 我还使用这个库进行虚假身份验证:https://github.com/webmotions/fake-authentication-jwtbearer。也许对你也有一些用处。 (2认同)