web-dev-qa-db-ja.com

EF Coreとメモリデータベース内の単体テスト

ASP.NET Core 2.2、EF Core、MOQを使用しています。次のコードでわかるように、2つのテストがあり、両方を一緒に実行しています。両方のデータベース名が「MovieListDatabase」で、テストの1つで次のメッセージのエラーが発生しました。

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

私はそれぞれを個別に実行すると、両方とも合格します。

また、「MovieListDatabase1」と「MovieListDatabase2」のように、両方のテストで異なるデータベース名を使用し、両方を一緒に実行すると、再び成功します。

2つの質問があります。なぜこれが起こるのですか?また、コードをリファクタリングして両方のテストでインメモリデータベースを再利用し、テストを少し見栄えよくする方法を教えてください。

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

そして、これはリポジトリクラスです

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

ありがとう

5
MarcosF8

このディスカッションに追加のソリューションを追加して、テストケースの独自の動作について説明したいと思います。

最も簡単な方法は、コンテキストファクトリを作成し、一意のデータベース名で開始することです。

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


            return new SampleContext(options);
        }
     }

メモリコンテキストで処理するときに静的データを使用しないでください。メモリデータベースコンテキストでは、データベース名が変わっていても、前のコンテキストからすべてのデータをマウントしようとします。

1
Jend DimShu

テストでは、フィクスチャクラスを使用して大きなエラーが発生しています。

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:データベースプロバイダーがこのDbContext用に構成されていません。プロバイダーを構成するには、DbContext.OnConfiguringメソッドをオーバーライドするか、アプリケーションサービスプロバイダーでAddDbContextを使用します。 AddDbContextを使用する場合は、DbContext型がそのコンストラクターでDbContextOptionsオブジェクトを受け入れ、DbContextの基本コンストラクターに渡すことも確認してください。 ----次のコンストラクタパラメータには一致するフィクスチャデータがありませんでした:MovieSeedDataFixtureフィクスチャ

フィクスチャクラスを使用するために空のコンストラクタを作成しましたが、オプション付きのコンストラクタを使用する必要があると思います。

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

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

    }

    public DbSet<Movie> Movies { get; set; }
}
0
MarcosF8