How can I reset an EF7 InMemory provider between unit tests?

C#Unit TestingEntity Framework-CoreMstest

C# Problem Overview


I am trying to use the EF7 InMemory provider for unit tests, but the persistent nature of the InMemory database between tests is causing me problems.

The following code demonstrates my issue. One test will work and the other test will always fail. Even though I set the _context to null between tests, the second test run will always have 4 records in it.

[TestClass]
public class UnitTest1
{
    private SchoolContext _context;

    [TestInitialize]
    public void Setup()
    {
        Random rng = new Random();
        
        var optionsBuilder = new DbContextOptionsBuilder<SchoolContext>();
        optionsBuilder.UseInMemoryDatabase();

        _context = new SchoolContext(optionsBuilder.Options);
        _context.Students.AddRange(
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
        );
        _context.SaveChanges();
    }

    [TestCleanup]
    public void Cleanup()
    {
        _context = null;
    }

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }

    [TestMethod]
    public void TestMethod2()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }
}

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class SchoolContext : DbContext
{
    public SchoolContext(DbContextOptions options) : base(options) { }

    public DbSet<Student> Students { get; set; }
}

C# Solutions


Solution 1 - C#

The following call will clear the in-memory datastore.

_context.Database.EnsureDeleted();

Solution 2 - C#

Bit late to the party, but i also ran into the same issue but what i ended up doing was.

Specifying a different database name for each test.

optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());

That way you dont have to add

_context.Database.EnsureDeleted();

in all your tests

Solution 3 - C#

Simply change your code definition of DbContextOptionsBuilder to be like following :

        var databaseName = "DatabaseNameHere";
        var dbContextOption = new DbContextOptionsBuilder<SchoolContext>()
                                    .UseInMemoryDatabase(databaseName, new InMemoryDatabaseRoot())
                                    .Options;

new InMemoryDatabaseRoot() creates a new database without the issue of Id's persisting. So you don't need now for :

       [TestCleanup]
       public void Cleanup()
       {
           _context = null;
       }

Solution 4 - C#

I would go with a combination of both answers. If tests run in parallel, you could have a database being deleted while you are in the middle of running another test, so I was seeing sporadic failures when running a 30+ tests.

Give it a random db name, and ensure it gets deleted when the test is completed.

public class MyRepositoryTests : IDisposable {
  private SchoolContext _context;

  [TestInitialize]
  public void Setup() {
    var options = new DbContextOptionsBuilder<ApplicationDbContext>()
      // Generate a random db name
      .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
      .Options;
      _context = new ApplicationDbContext(options);
  }

  [TestCleanup]
  public void Cleanup()
    _context.Database.EnsureDeleted(); // Remove from memory
    _context.Dispose();
  }
}

Solution 5 - C#

I use a DbContext fixture like the following

public class DbContextFixture 
    where TDbContext : DbContext
{
    private readonly DbContextOptions _dbContextOptions = 
        new DbContextOptionsBuilder()
            .UseInMemoryDatabase("_", new InMemoryDatabaseRoot())
            .Options;

    public TDbContext CreateDbContext()
    {
        return (TDbContext)(typeof(TDbContext)
            .GetConstructor(new[] { typeof(DbContextOptions) })
            .Invoke(new[] { _dbContextOptions }));
    }
}

you can now simply do

public class MyRepositoryTests : IDisposable {
    private SchoolContext _context;
    private DbContextFixture<ApplicationDbContext> _dbContextFixture;

    [TestInitialize]
    public void Setup() {
        _dbContextFixture = new DbContextFixture<ApplicationDbContext>();
        _context = _dbContextFixture.CreateDbContext();
        _context.Students.AddRange(
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
        );
        _context.SaveChanges();
    }

    [TestCleanup]
    public void Cleanup()
        _context.Dispose();
        _dbContextFixture = null;
    }

    [TestMethod]
    public void TestMethod1()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }

    [TestMethod]
    public void TestMethod2()
    {
        Assert.AreEqual(2, _context.Students.ToList().Count());
    }
}

This solution is thread-safe. See my blog for details.

Solution 6 - C#

The examples here achieve this through RemoveRange: https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.1

db.<Entity>.RemoveRange(db.<entity>);

Solution 7 - C#

Here is my 2 cent approach to keep each unit test isolated from each other. I am using C# 7, XUnit and EF core 3.1.

Sample TestFixture class.

public class SampleIntegrationTestFixture : IDisposable
    {

        public DbContextOptionsBuilder<SampleDbContext> SetupInMemoryDatabase()
            => new DbContextOptionsBuilder<SampleDbContext>().UseInMemoryDatabase("MyInMemoryDatabase");

 private IEnumerable<Student> CreateStudentStub()
            => new List<Student>
            {
            new Student { Id = rng.Next(1,10000), Name = "Able" },
            new Student { Id = rng.Next(1,10000), Name = "Bob" }
            };

        public void Dispose()
        {
        }
   }

Sample IntegrationTest class

 public class SampleJobIntegrationTest : IClassFixture<SampleIntegrationTestFixture >
 {
    private DbContextOptionsBuilder<SampleDbContext> DbContextBuilder { get; }
    private SampleDbContext SampleDbContext { get; set; }

 public SampleJobIntegrationTest(SampleIntegrationTestFixture 
 sampleIntegrationTestFixture )
  {
        SampleIntegrationTestFixture = sampleIntegrationTestFixture ;

        SampleDbContextBuilder = sampleIntegrationTestFixture .SetupInMemoryDatabase();
  }



  [Fact]
    public void TestMethod1()
    {
using(SampleDbContext = new SampleDbContext(SampleDbContextBuilder.Options))

        var students= SampleIntegrationTestFixture.CreateStudentStub();
            {
            SampleDbContext.Students.AddRange(students);

        SampleDbContext.SaveChanges();

  Assert.AreEqual(2, _context.Students.ToList().Count());

                SampleDbContext.Database.EnsureDeleted();
            }
      
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSailing JudoView Question on Stackoverflow
Solution 1 - C#natemcmasterView Answer on Stackoverflow
Solution 2 - C#R4nc1dView Answer on Stackoverflow
Solution 3 - C#Amin MohamedView Answer on Stackoverflow
Solution 4 - C#bradlis7View Answer on Stackoverflow
Solution 5 - C#Robert Jørgensgaard EngdahlView Answer on Stackoverflow
Solution 6 - C#Martin DView Answer on Stackoverflow
Solution 7 - C#maxspanView Answer on Stackoverflow