Модульное тестирование с EF Core и в базе данных памяти - PullRequest
0 голосов
/ 19 января 2019

Я использую ASP.NET Core 2.2, EF Core и MOQ.Как вы можете видеть в следующем коде, у меня есть два теста, и оба они выполняются вместе с именем базы данных «MovieListDatabase». Я получил ошибку в одном из тестов с этим сообщением:

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

Если язапускайте каждый по отдельности, оба они проходят.

А также, имея разные имена баз данных в обоих тестах, таких как «MovieListDatabase1» и «MovieListDatabase2», и запуская оба вместе, они проходят снова.

У меня естьдва вопроса: почему это происходит?и как я могу реорганизовать свой код, чтобы повторно использовать базу данных в памяти в обоих тестах и ​​сделать мой тест немного более чистым?

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

Спасибо

Ответы [ 4 ]

0 голосов
/ 19 января 2019

Спасибо, я сделал некоторые изменения в классе приборов и работает нормально, даже когда я запускаю оба теста вместе.

Вот изменение:

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();
    }
}
0 голосов
/ 19 января 2019

Похоже, вам может понадобиться классное приспособление .

Когда использовать: когда вы хотите создать отдельный контекст теста и поделиться им среди всех тестов в классе и очистить его после завершения всех тестов в классе.

Создайте отдельный класс для настройки любых данных, которые будут использоваться вашими тестами, и для их очистки после завершения тестов.

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

Затем используйте его в своих тестах, расширив интерфейс IClassFixture<T>.

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

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

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

    }
}
0 голосов
/ 19 января 2019

В тесте выдается большая ошибка с использованием класса прибора:

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 fixture

Я создал пустой конструктор для использования класса фикстуры, но, я думаю, ему нужно использовать конструктор с параметрами:

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

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

    }

    public DbSet<Movie> Movies { get; set; }
}
0 голосов
/ 19 января 2019

Вы можете решить проблему, добавив метку времени к имени базы данных.

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

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

Хотя я не видел этого в документации, кажется, что в памяти создается только одна база данных с данным именем.,Следовательно, если у вас такое же имя, такое исключение может произойти.

Подобное обсуждение существует в этой теме :

optionsBuilder.UseInMemoryDatabase("MyDatabase"); 

Это создает / используетбаза данных с именем «MyDatabase».Если UseInMemoryDatabase вызывается снова с тем же именем, то будет использоваться одна и та же база данных в памяти, что позволит ей совместно использовать несколько экземпляров контекста.

И эта проблема github также предлагает такой же подход для добавления уникальной строки с именем базы данных. Надеюсь, это поможет.

...