In order to get a better understanding of integration testing, I use IClassFixture<T> (from https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2#basic-tests-with-the-default-webapplicationfactory).
This works great for testing things like - page loading, form being displayed, getting the correct http status code, etc. But when testing an API, you'll want some seed data. To do so EF in-memory database is the typical approach. This is achieved via a custom web application factory where you can create a scope, request the appropriate service (i.e. dbcontext), and seed it (e.g. being https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-2.2#customize-webapplicationfactory).
I have an integration test project that is working and fully functional. But the nuances of how it works is still confusing to me.
Am I correct in assuming that when you create a CustomWebApplicationFactory, essentially you are creating a custom "Program.cs" (i.e. the typical entry point into the application) where you are free to add in additional testing services/filters as needed?
This is my implementation of web application factory for integration testing. My API has basic authentication for most endpoints so I added a global filter to bypass that. But what I am doing below is essentially the same in my Program.cs in my actual API (the only difference being I don't add the fake user and global anonymous filter). So I am lead to believe that my above point stands true. Is this a correct assumption?
Another point I wanted to verify is that in an actual unit test, I can replace a service with a mock. Is this possible in an integration test where I can swap out the DI instance for a requested service to be a test service instead?
E.g. my app has a IUploadFileToAzure service. Instead of using UploadFileToAzure as the DI instance, can I replace that implementation with a TestUploadFileToAzure service in my integration test?
Registering a service multiple times takes the last registration of the service so I was wondering if that can be used as a workaround for my above point. Is this even recommended? I understand it defeats the purpose of testing a service but wanted to verify if that was possible. I tried testing this locally and it did not work.
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost
.CreateDefaultBuilder<Startup>(new string[0])
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, AddCustomMiddlewareStartupFilter>();
});
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder
.UseEnvironment("Development")
.ConfigureServices(services =>
{
services.AddMvc(opt =>
{
//add a global anonymous filter
opt.Filters.Add(new AllowAnonymousFilter());
//add a filter for adding a fake claimsprincipal so that the user service
//correctly identifies the user
opt.Filters.Add(new FakeClaimsPrincipalFilter(true, false));
});
services.AddEntityFrameworkInMemoryDatabase();
// Create a new service provider.
var provider = services
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Add a database context using an in-memory
// database for testing.
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("TestDb");
options.UseInternalServiceProvider(provider);
});
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database context
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var apiDb = scopedServices.GetRequiredService<AppDbContext>();
// Ensure the database is created.
apiDb.Database.EnsureCreated();
}
});
}
}