I have a working web API that I recently updated to use JWT auth and, while it is working when I run it normally, I can't seem to get my integration tests to work.
I wanted to start working on integrating a token generation option for my integration tests, but I can't even get them to throw a 401.
When I run any of my preexisting integration tests that work without JWT in the project, I'd expect to get a 401 since I don't have any auth info, but am actually getting a System.InvalidOperationException : Scheme already exists: Bearer
error.
I'd assume this is happening because of the way that WebApplicationFactory
works by running its ConfigureWebHost
method runs after the Startup class' ConfigureServices
method and when i put a breakpoint on my jwt service, it does indeed get hit twice, but given that this is how WebApplicationFactory
is built, I'm not sure what the recommended option here is. Of note, even when I remove one of the services I still get the error:
var serviceDescriptor = services.FirstOrDefault(descriptor => descriptor.ServiceType == typeof(JwtBearerHandler));
services.Remove(serviceDescriptor);
My WebApplicationFactory
is based on the eshopwebapi factory:
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();
}
}
This is my service registration:
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"));
});
}
}
And this is my integration test (that I would expect to currently throw a 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());
}
}