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

后端 未结 7 1184
忘掉有多难
忘掉有多难 2021-01-31 13:01

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 d

相关标签:
7条回答
  • 2021-01-31 13:35

    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;
           }
    
    0 讨论(0)
  • 2021-01-31 13:35

    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.

    0 讨论(0)
  • 2021-01-31 13:36

    The following call will clear the in-memory datastore.

    _context.Database.EnsureDeleted();
    
    0 讨论(0)
  • 2021-01-31 13:52

    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();
                }
          
        }
    
    0 讨论(0)
  • 2021-01-31 13:55

    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

    0 讨论(0)
  • 2021-01-31 13:57

    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();
      }
    }
    
    0 讨论(0)
提交回复
热议问题