Wie kann ich einen EF7 InMemory-Anbieter zwischen Unit-Tests zurücksetzen?

78

Ich versuche, den EF7 InMemory-Anbieter für Komponententests zu verwenden, aber die Beständigkeit der InMemory-Datenbank zwischen den Tests verursacht mir Probleme.

Der folgende Code zeigt mein Problem. Ein Test funktioniert und der andere Test schlägt immer fehl. Obwohl ich den _context zwischen den Tests auf null gesetzt habe, enthält der zweite Testlauf immer 4 Datensätze.

[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; }
}
Segeln Judo
quelle

Antworten:

105

Der folgende Aufruf löscht den speicherinternen Datenspeicher.

_context.Database.EnsureDeleted();
Natemcmaster
quelle
Danke dafür, es hat meine Probleme gelöst. Ich habe zunächst optionsBuilder.UseInMemoryDatabase ausprobiert (persist: false); Dies wurde aus EFCore entfernt und stieß dann auf eine andere mögliche Lösung für unterschiedliche Kontexte zwischen Tests: docs.efproject.net/en/latest/miscellaneous/testing.html Ich bevorzuge jedoch die Einfachheit der ausgewählten Antwort für die Testwurzelzusammensetzung
Matt Sanders
20
Dies scheint die Identifizierungsspalte der In-Memory-Datenbank nicht zurückzusetzen. Wenn Sie also die Daten mit einer Zeile säen, wird beim ersten Test die Zeile mit der ID 1, beim zweiten Test 2 usw. angezeigt. Ist dies beabsichtigt?
Schmied
3
Es ist 2019 und das Problem, dass die IDs auch nach dem Löschen und Neuerstellen der Datenbank bestehen bleiben, ist immer noch ein Problem!
Tom
Nett. Dies hat mein Problem behoben! Ich dachte, mein Fehler sei, dass meine Tests parallel ausgeführt wurden, aber in Wirklichkeit wurde die In-Memory-Datenbank nicht ordnungsgemäß gelöscht.
Thorkil Værge
Ich würde vorschlagen, auch R4nc1d Antwort unten zu verwenden. Sie entfernen die Datenbank aus dem Speicher UND stellen außerdem sicher, dass Sie jedes Mal eine neue Datenbank mit einer neuen Identifizierungsspalte erhalten. Verwenden Sie einfach [TearDown](das läuft nach jedem Test).
CularBytes
33

Etwas spät zur Party, aber ich bin auch auf das gleiche Problem gestoßen, aber am Ende habe ich es getan.

Angabe eines anderen Datenbanknamens für jeden Test.

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

Auf diese Weise müssen Sie nicht hinzufügen

_context.Database.EnsureDeleted();

in all deinen Tests

R4nc1d
quelle
7
Lässt das sie nicht noch in Erinnerung?
Sander
1
Es wird ja im Speicher leben, aber wenn Sie Ihren Kontext in using-Anweisungen einschließen, wird es automatisch entsorgt.
R4nc1d
1
Dies ist eine großartige Ergänzung zur Top-Antwort und kann verwendet werden, um das Zurücksetzen der Identifizierungsspalte zu lösen. Ich würde immer noch verwenden EnsureDeleted, muss dies nur einmal in der [TearDown]Methode hinzufügen , die nach jedem Test ausgeführt wird, so dass kein großer Schmerz.
CularBytes
7

Ändern Sie einfach Ihre Codedefinition von DbContextOptionsBuilder wie folgt:

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

new InMemoryDatabaseRoot () erstellt eine neue Datenbank, ohne dass das Problem der ID bestehen bleibt . Sie brauchen jetzt also nicht für:

       [TestCleanup]
       public void Cleanup()
       {
           _context = null;
       }
Amin Mohamed
quelle
+1 für den InMemoryDatabaseRoot. Wenn Sie jedoch nur TestCleanup verwenden und den Kontext auf null setzen und einen neuen Kontext neu erstellen (vorausgesetzt, Sie verwenden denselben Datenbanknamen und verwenden InMemoryDatabaseRoot nicht), erhalten Sie in jedem TestInitialize dieselbe speicherinterne Datenbank.
Bobwah
2

Ich würde mit einer Kombination beider Antworten gehen. Wenn Tests parallel ausgeführt werden, wird möglicherweise eine Datenbank gelöscht, während Sie gerade einen weiteren Test ausführen. Daher traten beim Ausführen von mehr als 30 Tests sporadische Fehler auf.

Geben Sie ihm einen zufälligen Datenbanknamen und stellen Sie sicher, dass er nach Abschluss des Tests gelöscht wird.

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();
  }
}
bradlis7
quelle
2

Ich benutze ein DbContextGerät wie das folgende

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 }));
    }
}

Sie können jetzt einfach tun

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());
    }
}

Diese Lösung ist threadsicher. Siehe meinen Blog für Details.

Robert Jørgensgaard Engdahl
quelle
0

Hier ist mein 2-Cent-Ansatz, um jeden Unit-Test voneinander isoliert zu halten. Ich verwende C # 7, XUnit und EF Core 3.1.

Beispiel-TestFixture-Klasse.

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()
        {
        }
   }

Beispiel für eine IntegrationTest-Klasse

 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();
            }
      
    }
maxspan
quelle