JonPSmith / EfCore.TestSupport

Tools for helping in unit testing applications that use Entity Framework Core
https://www.thereformedprogrammer.net/new-features-for-unit-testing-your-entity-framework-core-5-code/
Other
352 stars 53 forks source link

Issues with possible unintended re-use of SQLite in-memory DbContext? #50

Closed kakins closed 1 year ago

kakins commented 2 years ago

Does SQLite in-memory by default re-use DbContext instances?

I'm not sure if this is a more general SQLite issue or related specifically to EfCore.TestSupport.

Here's my general test setup...

public class MyDbFixture
{
   public MyDbFixture()
   {
        // A custom `IDateTimeAdapter` to make testing easier with .NET DateTime
        var now = DateTime.UtcNow;
        TestDateTime = new Mock<IDateTimeAdapter>();
        TestDateTime
             .SetupGet(dt => dt.UtcNow)
             .Returns(now);
   }

   // All db tests will use this method to create the DbContext
   // The same mocked instance of `IDateTimeAdapter` is passed in, used for setting default values for date columns
   public MyDbContext CreateDbContext()
   {
      var options = SqliteInMemory.CreateOptions<MyDbContext>();
      var context = new MyDbContext(options, TestDateTime);
      context.Database.EnsureDeleted();
      context.Database.EnsureCreated();
      return context;
   }
}

Running the following tests presents no problem....

public class MyTests1
{
   public MyTestsA(MyDbFixture fixture)
   {
      _fixture = fixture;
   }

   public async Task MyTest1()
   {
      using var dbContext = _fixture.CreateDbContext()

      .... do testing stuff
   }

   public async Task MyTest2()
   {
      using var dbContext = _fixture.CreateDbContext()

      .... do testing stuff
   }
}

However, adding this test will cause the previous tests to occasionally fail when all tests are run together

public class MyTestsB
{
   public async Task MyOtherTest()
   {
        var options = SqliteInMemory.CreateOptions<MyDbContext>();

        // No Mock Setup for IDateTimeAdapter
        using var context = new OrderPollingDbContext(options, new Mock<IDateTimeAdapter>().Object);

        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        .... do testing stuff
   }
}

I notice the following:

JonPSmith commented 2 years ago

Hi @kakins,

I think your problem is because the connection is closes by EFCore.TestSupport. That's because theSqliteInMemory static methods creates an disposable version of the DbContextOptions<TContext> options. When the context is disposed the options are disposed which closes the connection, which will dispose of the in-memory database.

I suggest you try turning off the dispose using the code below

    var options = SqliteInMemory.CreateOptions<BookContext>();
    options.TurnOffDispose();

For more information / appraoches have a look at Working with multiple context instances - WARNING!.

Please close this issue if this fixes your problem.

kakins commented 2 years ago

Ok I will give that a try shortly.

But I'm a little confused. If the connection is disposed, that seems to be the opposite of what I'm experiencing. It seems like the context is not being disposed properly, but rather the "wrong" instance of a DbContext is being used in unrelated tests.

JonPSmith commented 2 years ago

Sorry, but Its hard to diagnose your problem from what you say.

I suggest you try creating the DbContext (without turning off the dispose) in each test and see whether that works. That will tell you if the problem is because you are creating the fixture.

kakins commented 2 years ago

Understood. It can be a little hard to describe also. But in short it seems like the "wrong" db context is sometimes used in each test, when running multiple tests in parallel using the VS test runner. I've seen this with a fixture and without a fixture, when I am creating a new db context per test case. I'll see if I can put together a little sample project or something to demonstrate the issue more clearly.

kakins commented 2 years ago

Just updating here to let you know I haven't forgotten. I'm still seeing this issue in multiple projects but just haven't had time to put together a small repro. I will try to do it eventually 🤞