Тестирование базового уникального индекса Entity Framework - PullRequest
0 голосов
/ 26 марта 2019

У меня есть класс модели:

public class Work
{
    public long Id { get; set; }

    [Required]
    public string Name { get; set; }
}

Я хочу, чтобы Work.Name было уникальным, поэтому я определяю DbContext:

public class MyDbContext : DbContext
{
    public MyDbContext () : base() { }
    public MyDbContext (DbContextOptions<MyDbContext > options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Work>(entity =>
            entity.HasIndex(e => e.Name).IsUnique()
        );
    }
    public DbSet<Work> Works { get; set; }
}

И я хочу проверитьэто, так что у меня есть такой тест:

[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
    var work = new Work
    {
        Name = "Test Work"
    };

    using (var context = new MyDbContext (options))
    {
        context.Works.Add(work);
        context.SaveChanges();
    }

    using (var context = new MyDbContext (options))
    {
        context.Works.Add(work);
        context.SaveChanges();
    }

    using (var context = new MyDbContext (options))
    {
         Assert.Equal(1, context.Works.Count());
    }
}

(объект option содержит настройки для InMemoryDatabase)

Я не знаю, что проверить, нотест не пройден в Assert, а не во втором SaveChanges().База данных (context) содержит два объекта с одинаковым Name.

Я просмотрел все соответствующие вопросы, но не увидел, чтобы кто-нибудь отвечал на мои вопросы.

Ответы [ 2 ]

1 голос
/ 26 марта 2019

Как отмечали другие, провайдер базы данных InMemory игнорирует все возможные ограничения.
Тогда я бы предложил использовать провайдера Sqlite с функцией "in-memory", которая выдаст исключение для дублирующихся уникальных ключей.

public MyDbContext CreateSqliteContext()
{
    var connectionString = 
        new SqliteConnectionStringBuilder { DataSource = ":memory:" }.ToString();
    var connection = new SqliteConnection(connectionString);
    var options = new DbContextOptionsBuilder<MyDbContext>().UseSqlite(connection);

    return new MyDbContext(options);
}

private void Insert(Work work)
{
    using (var context = CreateSqliteContext())
    {
        context.Works.Add(work);
        context.SaveChanges();
    }    
}

[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
    var work1 = new Work { Name = "Test Work" };
    var work2 = new Work { Name = "Test Work" };

    Insert(work1);

    Action saveDuplicate = () => Insert(work2);

    saveDuplicate.Should().Throw<DbUpdateException>(); // Pass Ok
}
0 голосов
/ 26 марта 2019

Тест не пройден, так как второй SaveChanges() сгенерирует исключение из базы данных, сообщающее, что вы не можете добавить другой элемент, поскольку он уже содержит объект с этим Name.

Уникальные ограничения не применяются бесшумно. Вместо этого, попытка добавить дублирующее значение вызовет исключение, когда вы попытаетесь это сделать. Это сделано для того, чтобы вы могли на самом деле реагировать на это, вместо того, чтобы замечать это после факта (когда вы видите, что данных, которые вы пытались добавить, там нет).

Вы можете проверить это с помощью Assert.Throws:

[Fact]
public void Post_InsertDuplicateWork_ShouldThrowException()
{
    var work = new Work
    {
        Name = "Test Work"
    };

    using (var context = new MyDbContext (options))
    {
        context.Works.Add(work);
        context.SaveChanges();
    }

    using (var context = new MyDbContext (options))
    {
        context.Works.Add(work);

        Assert.Throws<Exception>(() => context.SaveChanges());
    }
}

Вы также можете указать точное исключение там (я не помню поверх моей головы, какое именно исключение оно там выбрасывает), и вы также можете назначить его переменной (Assert.Throws() возвращает исключение) и проверьте сообщение об исключении, чтобы убедиться, что это именно то исключение, которое вы ожидаете.

...