Best way to Inject SeedData into Custom WebApplicationFactory

asp.net-core c# entity-framework-core

Question

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.

1
0
1/15/2019 3:29:12 PM

Accepted Answer

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.

0
1/15/2019 3:54:50 PM

Popular Answer

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();


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow