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 makes it easy to validate what EF is doing behind the scenes and ensures that no important configuration is missed, such as accidentally leaving a column as 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 is to have a test that asserts the generated SQL using snapshot testing. When the schema changes, the test fails and the new schema becomes visible in the code review. You can approve the new snapshot once you are satisfied 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, pass this string to the snapshot testing library. It compares the string with the stored snapshot and, if they differ, fails the test and updates the snapshot automatically. The maintenance cost is minimal.

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, 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*/ "");
    }
}

Run the tests and the snapshot will be updated automatically:

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



        """);
}

Every time the schema changes, the test fails and updates the snapshot, making it easy to review and validate schema changes during code reviews.

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

Follow me:
Enjoy this blog?