I have plagiarized the below code from the mightysoft docs site on integration testing and adapted it slightly to meet my needs:
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
private readonly SeedDataClass _seed;
public CustomWebApplicationFactory(SeedDataClass seed)
{
_seed = seed;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.UseEnvironment("Development");
builder.ConfigureServices(services =>
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
services.AddSingleton(_seed);
services.AddDbContextPool<GatewayContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
options.EnableSensitiveDataLogging();
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<GatewayContext>();
var logger = scopedServices
.GetRequiredService<ILogger<CustomWebApplicationFactory<TStartup>>>();
var seed = scopedServices.GetRequiredService<SeedDataClass>();
db.Database.EnsureCreated();
try
{
seed.InitializeDbForTests(db);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the database with test messages. Error: {ex.Message}");
}
}
});
}
}
To be used like in a test like:
_client = new CustomWebApplicationFactory<Startup>(new SeedDataClass()).CreateClient();
And this all works but I am looking to add generics to the custom web app factory class and move this code into a nuget package I am working on for internal testing work.
Something like this:
public class CustomWebApplicationFactory<TStartup, TContext>
: WebApplicationFactory<TStartup>
where TStartup : class
where TContext : DbContext
I am stuck on how to provide/inject the SeedDataClass
class instance into my new generic custom web app factory.
This is where I was going with this:
Ammended factory:
public class GenericWebApplicationFactory<TStartup, TContext, TSeed>
: WebApplicationFactory<TStartup>
where TStartup : class
where TContext : DbContext
where TSeed : class, ISeedDataClass
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.UseEnvironment("Development");
builder.ConfigureServices(services =>
{
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
services.AddSingleton<ISeedDataClass,TSeed >();
services.AddDbContextPool<TContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
options.EnableSensitiveDataLogging();
});
var sp = services.BuildServiceProvider();
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<TContext>();
var logger = scopedServices.GetRequiredService<ILogger<GenericWebApplicationFactory<TStartup, TContext, TSeed>>>();
var seeder = scopedServices.GetRequiredService<ISeedDataClass>();
db.Database.EnsureCreated();
try
{
seeder.InitializeDbForTests();
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred seeding the database with test messages. Error: {ex.Message}");
}
}
});
}
}
Ammended usage:
_client = new GenericWebApplicationFactory<Startup, GatewayContext, SeedDataClass>().CreateClient();
With example seed class:
public interface ISeedDataClass
{
void InitializeDbForTests();
}
public class SeedDataClass : ISeedDataClass
{
private readonly GatewayContext _db;
public SeedDataClass(GatewayContext db)
{
_db = db;
}
public void InitializeDbForTests()
{
_db.Users.AddRange(
// add some users here
);
_db.SaveChanges(true);
}
}
Now, I can seed the in memory database however I see fit, per project where it is employed and my GenericWebApplicationFactory
can now be pushed into a helper lib/nuget package which be re-used in other projects.
If you are just trying to adapt a similar constructor to the former implementation of your CustomWebApplicationFactory<TStartup>
class
_client = new CustomWebApplicationFactory<Startup>(new SeedDataClass()).CreateClient();
then your new constructor would look like so:
public class CustomWebApplicationFactory<TStartup, TContext> : WebApplicationFactory<TStartup>
where TStartup : class
where TContext : DbContext
{
private readonly SeedDataClass _seed;
public CustomWebApplicationFactory(SeedDataClass seed)
{
if (seed == null) throw new ArgumentNullException(nameof(seed));
_seed = seed;
}
}
and then update your call to the constructor like so
new CustomWebApplicationFactory<Startup, YourDbContext>(new SeedDataClass()).CreateClient();