Using Snapshot Testing to validate EF Core schema

 
 
  • Gérald Barré

When using Entity Framework Core, I prefer to view the generated schema as SQL. This allows me to easily validate what EF is doing behind the scenes and ensures that no attributes are missed, such as accidentally using nvarchar(max).

My main objectives are:

  • Be notified when the schema changes
  • Review the new schema while editing it and during code reviews

The solution I use is use is to have a test that assert the generated SQL with snapshot testing. If the schema changed, the test will fail and you can see the new schema in the code review. You can approve the new snapshot if you are happy with the changes.

There are multiple solutions for snapshot testing in .NET. You can use Verify or Meziantou.Framework.InlineSnapshotTesting. In this post, I will show you how to use Meziantou.Framwork.InlineSnapshotTesting but use the one you prefer. The end-result would be very similar.

The first step is to generate the schema as a string from the EF Core model. Then, you just need to pass this string to the snapshot testing library. The library will compare the string with the snapshot and if it's different, it will fail the test and create the new snapshot automatically. So, the maintenance cost is very low.

Let's create a solution:

Shell
dotnet new classlib --output MyBlog
dotnet add MyBlog package Microsoft.EntityFrameworkCore.SqlServer

dotnet new xunit --output MyBlog.Tests
dotnet add MyBlog.Tests reference MyBlog
dotnet add MyBlog.Tests package Meziantou.Framework.InlineSnapshotTesting

dotnet new sln
dotnet sln add MyBlog
dotnet sln add MyBlog.Tests

In the class library, you can define the model:

C#
using Microsoft.EntityFrameworkCore;

public class BlogContext: DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=EFTesting;Trusted_Connection=True;MultipleActiveResultSets=true");
    }

    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Then, you can use the following code to generate the schema and compare it with the snapshot:

C#
using Meziantou.Framework.InlineSnapshotTesting;
using Microsoft.EntityFrameworkCore;

namespace TestProject1;

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
        var context = new BlogContext();
        var script = context.Database.GenerateCreateScript();
        InlineSnapshot.Validate(script, /*lang=sql*/ "");
    }
}

You can now run the tests and you'll see the snapshot is updated:

Shell
dotnet test
C#
[Fact]
public void Test1()
{
    var context = new BlogContext();
    var script = context.Database.GenerateCreateScript();
    InlineSnapshot.Validate(script, /*lang=sql*/ """
        CREATE TABLE [Blogs] (
            [BlogId] int NOT NULL IDENTITY,
            [Name] nvarchar(max) NOT NULL,
            [Url] nvarchar(max) NOT NULL,
            CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId])
        );
        GO


        CREATE TABLE [Posts] (
            [PostId] int NOT NULL IDENTITY,
            [Title] nvarchar(max) NOT NULL,
            [Content] nvarchar(max) NOT NULL,
            [BlogId] int NOT NULL,
            CONSTRAINT [PK_Posts] PRIMARY KEY ([PostId]),
            CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([BlogId]) ON DELETE CASCADE
        );
        GO


        CREATE INDEX [IX_Posts_BlogId] ON [Posts] ([BlogId]);
        GO



        """);
}

Everytime the database changes, the test will fail and update the snapshot. This way, you can easily validate the schema changes in code reviews.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub