0

I have a test project that uses IClassFixture of a generic factory class. For example

public class WeatherForecastAcceptanceTest
    : IClassFixture<WebApi1ApplicationFactory<Startup>>, IDisposable
{
    .....
}

after the Startup class executes ConfigureServices and Configure methods it executes the ConfigureWebHost where I remove the original DbContext and add a new one that runs in memory.

public class WebApi1ApplicationFactory<TStartup>
    : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            //remove the injected DbContext and inject in-memory
            services.RemoveAll(typeof(DbContext));
            var connection = new SqliteConnection("Data Source=:memory:");
            services.AddDbContext<WebApi1DbContext>(
                options => options.UseSqlite(connection));

            var sp = services.BuildServiceProvider();
            using (var scope = sp.CreateScope())
            {
                //ERROR HERE WITH THE RESOLVED DbContext
                using (var dbContext = scope.ServiceProvider
                    .GetRequiredService<WebApi1DbContext>())
                {
                    try
                    {
                        dbContext.Database.OpenConnection();
                        dbContext.Database.EnsureCreated();
                    }
                    catch (Exception ex)
                    {
                        throw;
                    }
                }
            }
        });
    }
}

The DbContext that I am resolving has the original connectionString instead of InMemory, as a result, all my testings are inserting content on my original database.

Here is how I am using my WebApplicationFactory

public class WeatherForecastAcceptanceTest : IClassFixture<WebApi1ApplicationFactory<Startup>>, IDisposable
    {
        private WebApi1ApplicationFactory<Startup> _factory;
        private HttpClient _client;

        public WeatherForecastAcceptanceTest(WebApi1ApplicationFactory<Startup> factory)
        {
            _factory = factory;
            _client = factory.CreateClient();
        }

        [Fact]
        public async Task GetAll_ReturnsElements_WhenPostWasExecutedSucessfully()
        {
            // Arrange
        var weatherForecastForCreationDto = CreateRandomWeatherForecastForCreationDto();

        var content = new JsonContent(weatherForecastForCreationDto);
        //setting the content-type header
        content.Headers.ContentType = new MediaTypeWithQualityHeaderValue(HttpMediaTypes.WeatherForecastType1);

        //create an object
        using (var client = _factory.CreateClient())
        {
            //act
            var responsePost = await _client.PostAsync(ApiRoutes.WeatherForecast.CreateWeatherForecast, content);
            //assert
            responsePost.StatusCode.Should().Be(StatusCodes.Status201Created);
        }

        //check that the object was inserted
        using (var client = _factory.CreateClient())
        {
            _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(HttpMediaTypes.WeatherForecastType1));
            // Act
            var response = await _client.GetAsync(ApiRoutes.WeatherForecast.GetWeatherForecast);

            // Assert
            response.StatusCode.Should().Be(StatusCodes.Status200OK);
            var returnedGet = await response.Content.ReadAsAsync<WeatherForecastDto[]>();
            returnedGet.Should().Contain(dto => dto.Summary == weatherForecastForCreationDto.Summary);
        }
        }

        public void Dispose()
        {
            _client?.Dispose();
            _factory?.Dispose();
        }
    }

How can I resolve the in-memory DbContext that was injected?

Zinov
  • 3,817
  • 5
  • 36
  • 70
  • 1
    Based on the code you posted, I see no reason why resolving `WebApi1DbContext` wouldn't get the last-registered `WebApi1DbContext`. This means that there is something going on that is not present in the posted code. Try building an [MRE](https://stackoverflow.com/help/minimal-reproducible-example) and update the question with that code. Without that information, I'm afraid that your question will get closed without getting a satisfactory answer. – Steven Jul 02 '20 at 08:17
  • I will try to post a github repo with the code – Zinov Jul 02 '20 at 14:48
  • Hi Zinov, please don't do that. Stack Overflow questions must stand on their own. This means that *all* the relevant code must be part of your post. To ensure you post the minimal amount of code that reproduces the issue, try creating a console application that demonstrates the problem. – Steven Jul 02 '20 at 14:50
  • @Steven, this can't be replicated on a console application, you need 2 projects at least, one for your API and that other for the test. I think with the code above is more than enough to replicate the issue, someone that knows about TestServer and IClassFixture on .net core, should replicate it with no issues. I will update my question with more details to bring more context. Thanks – Zinov Jul 02 '20 at 17:08

1 Answers1

0

After dealing with this problem for a while, based on the documentation this is what they are suggesting

  1. On your startup read a test environment variable, like you are doing with production or development, and inject the correct DbContext for that.

For example to inject an on memory DBContext with Sqlite.

    if (_env.IsEnvironment("Test"))
    {
         var connection = new SqliteConnection(connectionDb);
         services.AddDbContextPool<WebApi1DbContext>(options => options.UseSqlite(connection));
    }
  1. Instead of trying to remove the DbContext in that way, the idea is try to set the environment variable to the WebApplicationFactory because the AddDbContext sets more dependencies on the container that RemoveAll is not considering.
public class WebApi1ApplicationFactory<TStartup>
        : WebApplicationFactory<TStartup> where TStartup : class
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.UseEnvironment("Test");

            builder.ConfigureServices(services =>
            {
                //DbContext in-memory is injected taking in consideration the Test environment.
                var sp = services.BuildServiceProvider();

                using (var scope = sp.CreateScope())
                {
                    using (var dbContext = scope.ServiceProvider.GetRequiredService<WebApi1DbContext>())
                    {
                        try
                        {
                            dbContext.Database.OpenConnection();
                            dbContext.Database.EnsureCreated();
                        }
                        catch (Exception ex)
                        {
                            //Log errors or do anything you think it's needed
                            throw;
                        }
                    }
                }
            });
        }
    }

This action will allow you to use the DbContext to retrieve the DbContext you want for the test environment.

Zinov
  • 3,817
  • 5
  • 36
  • 70