简体   繁体   中英

Entity Framework Core Unique Index testing

I have a model class:

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

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

I want this Work.Name will be unique, so I define the 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; }
}

And I want to test this, so I have a test like this:

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

( The option object contains settings for InMemoryDatabase )

I don't really know what to check, but the test failed in the Assert , not in the second SaveChanges() . The database (the context ) contains two objects with the same Name .

I went over all the relevant questions, but I did not see anyone answering what I was asking.

As others pointed out InMemory database provider ignore all possible constraints.
My suggestion would be then to use Sqlite provider with "in-memory' feature, which will throw an exception for duplicate unique keys.

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
}

The test fails because the second SaveChanges() will throw an exception from the database that tells you that you cannot add another item because it already contains an object with that Name .

Unique constraints are not enforced silently. Instead, attempting to add a duplicate value will throw an exception when you try to do it. This is so that you can actually react to it, instead of only noticing it after the fact (when you see that the data you attempted to add is not there).

You can test that by using 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());
    }
}

You can also specify the exact exception there (I don't remember on top of my head which exception it exactly is that is thrown there), and you can also assign it to a variable ( Assert.Throws() returns the exception) and verify the exception message to make sure that this is the exact exception you expect.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM