Search code examples
c#entity-frameworkasp.net-coreintegration-testingin-memory-database

Can't seed data in mocked InMemory db in Integration tests


I have next test class with one integration test that calls simple api endpoint.

Here in Setup method I'm replacing real db to InMemory and trying to add info about two cities to db.

 public class TestControllerTest
 {
     private WebApplicationFactory<RentAPI.Program> _factory;
     private HttpClient _client;

     [SetUp]
     public void Setup()
     {
         _factory = new WebApplicationFactory<RentAPI.Program>().WithWebHostBuilder(builder =>
         {
             builder.ConfigureTestServices(services =>
             {
                 var dbContextDescriptor = services.SingleOrDefault(d =>
                     d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));

                 services.Remove(dbContextDescriptor);

                 services.AddDbContext<ApplicationDbContext>(options =>
                 {
                     options.UseInMemoryDatabase(Guid.NewGuid().ToString());
                 });

                 using var scope = services.BuildServiceProvider().CreateScope();
                 var db = scope.ServiceProvider.GetService<ApplicationDbContext>();

                 SeedData(db);
             });
         });

         _client = _factory.CreateClient();
     }

     [Test]
     public async Task Test_test()
     {

         var response = await _client.GetAsync("/Test");
         var stingResult = await response.Content.ReadAsStringAsync();

         Assert.That(stingResult, Is.EqualTo("3"));
     }

     [TearDown]
     public void TearDown()
     {
         _client.Dispose();
         _factory.Dispose();
     }

     public static void SeedData(ApplicationDbContext context)
     {
         context.Cities.AddRange(
             new City { Id = 1, Name = "City1" },
             new City { Id = 2, Name = "City2" }
         );

         context.SaveChanges();
     }
 }

and here is endpoint

[ApiController, Route("[controller]")]
public class TestController : ControllerBase
{
    private readonly IUnitOfWork _uow;

    public TestController(IUnitOfWork uow) => _uow = uow;

    [HttpGet]
    public async Task<ActionResult<string>> Test()
    {
        await _uow.CityRepository.AddAsync(new City() { Id = 99, Name = "Poko" });
        await _uow.CompleteAsync();

        var cities = await _uow.CityRepository.FindAllAsync();
        return cities.Count().ToString();
    }
}

The problem is that despite my attempts to add cities information to the database I use for my application, these records do not appear when I call endpoint in test.

here is test result:

  String lengths are both 1. Strings differ at index 0.
  Expected: "3"
  But was:  "1"
  -----------^

Solution

  • "Freeze" the database name by moving the Guid.NewGuid().ToString() out of the ConfigureTestServices and WithWebHostBuilder.

    Also I highly recommend to move the seeding out too since budling the service provider multiple times is discouraged and can lead to unwanted sideeffect:

     public void Setup()
     {
          var databaseName = Guid.NewGuid().ToString();
         _factory = new WebApplicationFactory<Program>().WithWebHostBuilder(builder =>
         {
             builder.ConfigureTestServices(services =>
             {
                 var dbContextDescriptor = services.SingleOrDefault(d =>
                     d.ServiceType == typeof(DbContextOptions<ApplicationDbContext>));
    
                 services.Remove(dbContextDescriptor);
    
                 services.AddDbContext<ApplicationDbContext>(options =>
                 {
                     options.UseInMemoryDatabase(databaseName);
                 });
             });
         });
        
         using var scope =  _factory.Services.CreateScope();;
         var db = scope.ServiceProvider.GetService<ApplicationDbContext>();
    
         SeedData(db);
         _client = _factory.CreateClient();
     }
    

    Your current code:

    using var scope = services.BuildServiceProvider().CreateScope();
    

    Builds a separate DI container which will have it's own set of services which will not relate to the ones used by test server (which will spin up it's own container).