Ich verwende ASP.NET Core 2.2, EF Core und MOQ. Wie Sie im folgenden Code sehen können, habe ich zwei Tests und beide zusammen mit beiden Datenbanknamen "MovieListDatabase" ausgeführt. Bei einem der Tests mit dieser Meldung ist ein Fehler aufgetreten:

Message: System.ArgumentException : An item with the same key has already 
been added. Key: 1

Wenn ich jeden einzeln laufen lasse, bestehen beide.

Wenn Sie in beiden Tests einen anderen Datenbanknamen haben, z. B. "MovieListDatabase1" und "MovieListDatabase2", und beide zusammen ausführen, wird er erneut bestanden.

Ich habe zwei Fragen: Warum passiert das? und wie kann ich meinen Code umgestalten, um die In-Memory-Datenbank in beiden Tests wiederzuverwenden und meinen Test ein bisschen sauberer aussehen zu lassen?

 public class MovieRepositoryTest
{
    [Fact]
    public void GetAll_WhenCalled_ReturnsAllItems()
    {

        var options = new DbContextOptionsBuilder<MovieDbContext>()
            .UseInMemoryDatabase(databaseName: "MovieListDatabase")
            .Options;

        // Insert seed data into the database using one instance of the context
        using (var context = new MovieDbContext(options))
        {
            context.Movies.Add(new Movie { Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" });
            context.Movies.Add(new Movie { Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action" });
            context.Movies.Add(new Movie { Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action" });
            context.SaveChanges();
        }

        // Use a clean instance of the context to run the test
        using (var context = new MovieDbContext(options))
        {
            var sut = new MovieRepository(context);
            //Act
            var movies = sut.GetAll();

            //Assert
            Assert.Equal(3, movies.Count());
        }
    }

    [Fact]
    public void Search_ValidTitlePassed_ReturnsOneMovie()
    {
        var filters = new MovieFilters { Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" };

        var options = new DbContextOptionsBuilder<MovieDbContext>()
            .UseInMemoryDatabase(databaseName: "MovieListDatabase")
            .Options;

        // Insert seed data into the database using one instance of the context
        using (var context = new MovieDbContext(options))
        {
            context.Movies.Add(new Movie { Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" });
            context.Movies.Add(new Movie { Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action" });
            context.Movies.Add(new Movie { Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action" });
            context.SaveChanges();
        }

        // Use a clean instance of the context to run the test
        using (var context = new MovieDbContext(options))
        {
            var sut = new MovieRepository(context);

            //Act
            //var movies = _sut.Search(_filters);
            var movies = sut.Search(filters);

            //Assert
            Assert.Single(movies);
        }
    }
}

Und das ist die Repository-Klasse

 public class MovieRepository: IMovieRepository
{
    private readonly MovieDbContext _moviesDbContext;
    public MovieRepository(MovieDbContext moviesDbContext)
    {
        _moviesDbContext = moviesDbContext;
    }

    public IEnumerable<Movie> GetAll()
    {
        return _moviesDbContext.Movies;
    }

    public IEnumerable<Movie> Search(MovieFilters filters)
    {
        var title = filters.Title.ToLower();
        var genre = filters.Genre.ToLower();
        return _moviesDbContext.Movies.Where( p => (p.Title.Trim().ToLower().Contains(title) | string.IsNullOrWhiteSpace(p.Title))
                                                   & (p.Genre.Trim().ToLower().Contains(genre) | string.IsNullOrWhiteSpace(p.Genre))
                                                   & (p.YearOfRelease == filters.YearOfRelease | filters.YearOfRelease == null)
                                             );
    }
}

Vielen Dank

19
MarcosF8 19 Jän. 2019 im 09:55

5 Antworten

Beste Antwort

Möglicherweise möchten Sie ein Klassengerät.

Verwendungszweck: Wenn Sie einen einzelnen Testkontext erstellen und für alle Tests in der Klasse freigeben möchten, und ihn nach Abschluss aller Tests in der Klasse bereinigen lassen möchten.

Erstellen Sie eine separate Klasse, um die Daten einzurichten, die Ihre Tests gemeinsam nutzen, und um sie zu bereinigen, wenn die Tests abgeschlossen sind.

public class MovieSeedDataFixture : IDisposable
{
    public MovieDbContext MovieContext { get; private set; } = new MovieDbContext();

    public MovieSeedDataFixture()
    {
        MovieContext.Movies.Add(new Movie { Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" });
        MovieContext.Movies.Add(new Movie { Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action" });
        MovieContext.Movies.Add(new Movie { Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action" });
        MovieContext.SaveChanges();
    }

    public void Dispose()
    {
        MovieContext.Dispose();
    }
}

Verwenden Sie es dann in Ihren Tests, indem Sie die Schnittstelle IClassFixture<T> erweitern.

public class UnitTests : IClassFixture<MovieSeedDataFixture>
{
    MovieSeedDataFixture fixture;

    public UnitTests(MovieSeedDataFixture fixture)
    {
        this.fixture = fixture;
    }

    [Fact]
    public void TestOne()
    {
        // use fixture.MovieContext in your tests

    }
}
5
vivat pisces 19 Jän. 2019 im 07:19

Sie können das Problem beheben, indem Sie den Zeitstempel mit dem Namen des Datenbanknamens anhängen.

var myDatabaseName = "mydatabase_"+DateTime.Now.ToFileTimeUtc();

var options = new DbContextOptionsBuilder<BloggingContext>()
                .UseInMemoryDatabase(databaseName: myDatabaseName )
                .Options;

Obwohl ich dies in der Dokumentation nicht gesehen habe, scheint nur eine Datenbank mit einem bestimmten Namen im Speicher erstellt zu sein. Wenn Sie denselben Namen haben, kann diese Art von Ausnahme auftreten.

Eine ähnliche Diskussion gibt es zu diesem Thread:

optionsBuilder.UseInMemoryDatabase("MyDatabase"); 

Dadurch wird eine Datenbank mit dem Namen "MyDatabase" erstellt / verwendet. Wenn UseInMemoryDatabase erneut mit demselben Namen aufgerufen wird, wird dieselbe speicherinterne Datenbank verwendet, sodass sie von mehreren Kontextinstanzen gemeinsam genutzt werden kann.

dieses Github -Problem schlägt ebenfalls den gleichen Ansatz vor, um eine eindeutige Zeichenfolge mit Datenbanknamen hinzuzufügen Hoffe das hilft.

2
Manoj Choudhari 19 Jän. 2019 im 07:13

Danke, ich habe einige Änderungen in der Fixture-Klasse vorgenommen und funktioniert einwandfrei, auch wenn ich beide Tests zusammen durchführe.

Hier ist die Änderung:

public class MovieSeedDataFixture : IDisposable
{
    public MovieDbContext MovieContext { get; private set; }

    public MovieSeedDataFixture()
    {
        var options = new DbContextOptionsBuilder<MovieDbContext>()
            .UseInMemoryDatabase("MovieListDatabase")
            .Options;

        MovieContext = new MovieDbContext(options);

        MovieContext.Movies.Add(new Movie { Id = 1, Title = "Movie 1", YearOfRelease = 2018, Genre = "Action" });
        MovieContext.Movies.Add(new Movie { Id = 2, Title = "Movie 2", YearOfRelease = 2018, Genre = "Action" });
        MovieContext.Movies.Add(new Movie { Id = 3, Title = "Movie 3", YearOfRelease = 2019, Genre = "Action" });
        MovieContext.SaveChanges();
    }

    public void Dispose()
    {
        MovieContext.Dispose();
    }
}
2
MarcosF8 19 Jän. 2019 im 08:06

Ich möchte nur eine zusätzliche Lösung für diese Diskussion hinzufügen und ein einzigartiges Verhalten in meinem Testfall erwähnen.

Am einfachsten ist es, eine Kontextfactory zu erstellen und mit einem eindeutigen Datenbanknamen zu initiieren.

   public static class ContextFactory
    {
        public static SampleContextCreateInMemoryContractContext()
        {
            var options = new DbContextOptionsBuilder<SchedulingContext>()
               .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
               .Options;


            return new SampleContext(options);
        }
     }

Vermeiden Sie die Verwendung statischer Daten, wenn Sie im Speicherkontext arbeiten. Im Speicherdatenbankkontext wird versucht, alle Daten aus dem vorherigen Kontext bereitzustellen, auch wenn sie einen anderen Datenbanknamen haben, seltsam :).

1
Jend DimShu 20 Aug. 2019 im 09:24

Der Test gibt einen großen Fehler bei der Verwendung der Fixture-Klasse:

Message: System.AggregateException : One or more errors occurred. (No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.) (The following constructor parameters did not have matching fixture data: MovieSeedDataFixture fixture)

---- System.InvalidOperationException: Für diesen DbContext wurde kein Datenbankanbieter konfiguriert. Ein Anbieter kann durch Überschreiben der DbContext.OnConfiguring-Methode oder durch Verwendung von AddDbContext auf dem Anwendungsdienstanbieter konfiguriert werden. Wenn AddDbContext verwendet wird, stellen Sie außerdem sicher, dass Ihr DbContext-Typ ein DbContextOptions-Objekt in seinem Konstruktor akzeptiert und es an den Basiskonstruktor für DbContext übergibt. ---- Die folgenden Konstruktorparameter hatten keine übereinstimmenden Fixture-Daten: MovieSeedDataFixture-Fixture

Ich habe einen leeren Konstruktor erstellt, um die Fixture-Klasse zu verwenden, aber ich denke, er muss den Konstruktor mit den folgenden Optionen verwenden:

public class MovieDbContext: DbContext
{
    public MovieDbContext()
    {
    }

    public MovieDbContext(DbContextOptions<MovieDbContext> options) : base(options)
    {

    }

    public DbSet<Movie> Movies { get; set; }
}
0
MarcosF8 19 Jän. 2019 im 07:53