# Verify.EntityFramework [![Discussions](https://img.shields.io/badge/Verify-Discussions-yellow?svg=true&label=)](https://github.com/orgs/VerifyTests/discussions) [![Build status](https://ci.appveyor.com/api/projects/status/g6njwv0aox62atu0?svg=true)](https://ci.appveyor.com/project/SimonCropp/verify-entityframework) [![NuGet Status](https://img.shields.io/nuget/v/Verify.EntityFramework.svg?label=Verify.EntityFramework)](https://www.nuget.org/packages/Verify.EntityFramework/) [![NuGet Status](https://img.shields.io/nuget/v/Verify.EntityFrameworkClassic.svg?label=Verify.EntityFrameworkClassic)](https://www.nuget.org/packages/Verify.EntityFrameworkClassic/) Extends [Verify](https://github.com/VerifyTests/Verify) to allow snapshot testing with EntityFramework. **See [Milestones](../../milestones?state=closed) for release notes.** ## NuGet packages * https://nuget.org/packages/Verify.EntityFramework/ * https://nuget.org/packages/Verify.EntityFrameworkClassic/ ## Enable Enable VerifyEntityFramework once at assembly load time: ### EF Core ```cs static IModel GetDbModel() { var options = new DbContextOptionsBuilder(); options.UseSqlServer("fake"); using var data = new SampleDbContext(options.Options); return data.Model; } [ModuleInitializer] public static void Init() { var model = GetDbModel(); VerifyEntityFramework.Initialize(model); } ``` snippet source | anchor The `GetDbModel` pattern allows an instance of the `IModel` to be stored for use when `IgnoreNavigationProperties` is called inside tests. This is optional, and instead can be passed explicitly to `IgnoreNavigationProperties`. ### EF Classic ```cs [ModuleInitializer] public static void Init() => VerifyEntityFrameworkClassic.Initialize(); ``` snippet source | anchor ## Recording Recording allows all commands executed by EF to be captured and then (optionally) verified. ### Enable Call `EfRecording.EnableRecording()` on `DbContextOptionsBuilder`. ```cs var builder = new DbContextOptionsBuilder(); builder.UseSqlServer(connection); builder.EnableRecording(); var data = new SampleDbContext(builder.Options); ``` snippet source | anchor `EnableRecording` should only be called in the test context. ### Usage To start recording call `EfRecording.StartRecording()`. The results will be automatically included in verified file. ```cs var company = new Company { Name = "Title" }; data.Add(company); await data.SaveChangesAsync(); Recording.Start(); await data .Companies .Where(_ => _.Name == "Title") .ToListAsync(); await Verify(); ``` snippet source | anchor Will result in the following verified file: ```txt { ef: { Type: ReaderExecutedAsync, HasTransaction: false, Text: SELECT [c].[Id], [c].[Name] FROM [Companies] AS [c] WHERE [c].[Name] = N'Title' } } ``` snippet source | anchor Sql entries can be explicitly read using `EfRecording.FinishRecording`, optionally filtered, and passed to Verify: ```cs var company = new Company { Name = "Title" }; data.Add(company); await data.SaveChangesAsync(); Recording.Start(); await data .Companies .Where(_ => _.Name == "Title") .ToListAsync(); var entries = Recording.Stop(); //TODO: optionally filter the results await Verify( new { target = data.Companies.Count(), entries }); ``` snippet source | anchor ### DbContext spanning `StartRecording` can be called on different DbContext instances (built from the same options) and the results will be aggregated. ```cs var builder = new DbContextOptionsBuilder(); builder.UseSqlServer(connectionString); builder.EnableRecording(); await using var data1 = new SampleDbContext(builder.Options); Recording.Start(); var company = new Company { Name = "Title" }; data1.Add(company); await data1.SaveChangesAsync(); await using var data2 = new SampleDbContext(builder.Options); await data2 .Companies .Where(_ => _.Name == "Title") .ToListAsync(); await Verify(); ``` snippet source | anchor ```txt { ef: [ { Type: ReaderExecutedAsync, HasTransaction: false, Parameters: { @p0 (Int32): 0, @p1 (String): Title }, Text: SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; INSERT INTO [Companies] ([Id], [Name]) VALUES (@p0, @p1); }, { Type: ReaderExecutedAsync, HasTransaction: false, Text: SELECT [c].[Id], [c].[Name] FROM [Companies] AS [c] WHERE [c].[Name] = N'Title' } ] } ``` snippet source | anchor ### Disabling Recording for an instance ```cs var company = new Company { Name = "Title" }; data.Add(company); await data.SaveChangesAsync(); Recording.Start(); await data .Companies .Where(_ => _.Name == "Title") .ToListAsync(); data.DisableRecording(); await data .Companies .Where(_ => _.Name == "Disabled") .ToListAsync(); await Verify(); ``` snippet source | anchor ```txt { ef: { Type: ReaderExecutedAsync, HasTransaction: false, Text: SELECT [c].[Id], [c].[Name] FROM [Companies] AS [c] WHERE [c].[Name] = N'Title' } } ``` snippet source | anchor ## ChangeTracking Added, deleted, and Modified entities can be verified by performing changes on a DbContext and then verifying the instance of ChangeTracking. This approach leverages the [EntityFramework ChangeTracker](https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.changetracking.changetracker). ### Added entity This test: ```cs [Test] public async Task Added() { var options = DbContextOptions(); await using var data = new SampleDbContext(options); var company = new Company { Name = "company name" }; data.Add(company); await Verify(data.ChangeTracker); } ``` snippet source | anchor Will result in the following verified file: ```txt { Added: { Company: { Id: 0, Name: company name } } } ``` snippet source | anchor ### Deleted entity This test: ```cs [Test] public async Task Deleted() { var options = DbContextOptions(); await using var data = new SampleDbContext(options); data.Add(new Company { Name = "company name" }); await data.SaveChangesAsync(); var company = data.Companies.Single(); data.Companies.Remove(company); await Verify(data.ChangeTracker); } ``` snippet source | anchor Will result in the following verified file: ```txt { Deleted: { Company: { Id: 0 } } } ``` snippet source | anchor ### Modified entity This test: ```cs [Test] public async Task Modified() { var options = DbContextOptions(); await using var data = new SampleDbContext(options); var company = new Company { Name = "old name" }; data.Add(company); await data.SaveChangesAsync(); data.Companies.Single() .Name = "new name"; await Verify(data.ChangeTracker); } ``` snippet source | anchor Will result in the following verified file: ```txt { Modified: { Company: { Id: 0, Name: { Original: old name, Current: new name } } } } ``` snippet source | anchor ## Queryable This test: ```cs var queryable = data.Companies .Where(_ => _.Name == "company name"); await Verify(queryable); ``` snippet source | anchor Will result in the following verified files: ### EF Core #### CoreTests.Queryable.verified.txt ```txt [ { Name: company name } ] ``` snippet source | anchor #### CoreTests.Queryable.verified.sql ```sql SELECT [c].[Id], [c].[Name] FROM [Companies] AS [c] WHERE [c].[Name] = N'company name' ``` snippet source | anchor ### EF Classic #### ClassicTests.Queryable.verified.txt ```txt SELECT [Extent1].[Id] AS [Id], [Extent1].[Content] AS [Content] FROM [dbo].[Companies] AS [Extent1] WHERE N'value' = [Extent1].[Content] ``` snippet source | anchor ## AllData This test: ```cs await Verify(data.AllData()) .AddExtraSettings( serializer => serializer.TypeNameHandling = TypeNameHandling.Objects); ``` snippet source | anchor Will result in the following verified file with all data in the database: ```txt [ { $type: Company, Id: 1, Name: Company1 }, { $type: Company, Id: 4, Name: Company2 }, { $type: Company, Id: 6, Name: Company3 }, { $type: Company, Id: 7, Name: Company4 }, { $type: Employee, Id: 2, CompanyId: 1, Name: Employee1, Age: 25 }, { $type: Employee, Id: 3, CompanyId: 1, Name: Employee2, Age: 31 }, { $type: Employee, Id: 5, CompanyId: 4, Name: Employee4, Age: 34 } ] ``` snippet source | anchor ## IgnoreNavigationProperties `IgnoreNavigationProperties` extends `SerializationSettings` to exclude all navigation properties from serialization: ```cs [Test] public async Task IgnoreNavigationProperties() { var options = DbContextOptions(); await using var data = new SampleDbContext(options); var company = new Company { Name = "company" }; var employee = new Employee { Name = "employee", Company = company }; await Verify(employee) .IgnoreNavigationProperties(); } ``` snippet source | anchor ### Ignore globally ```cs var options = DbContextOptions(); using var data = new SampleDbContext(options); VerifyEntityFramework.IgnoreNavigationProperties(); ``` snippet source | anchor ## WebApplicationFactory To be able to use [WebApplicationFactory](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1) for [integration testing](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests) an identifier must be used to be able to retrieve the recorded commands. Start by enable recording with a unique identifier, for example the test name or a GUID: ```cs protected override void ConfigureWebHost(IWebHostBuilder webBuilder) { var dataBuilder = new DbContextOptionsBuilder() .EnableRecording(name) .UseSqlite($"Data Source={name};Mode=Memory;Cache=Shared"); webBuilder.ConfigureTestServices( _ => _.AddScoped( _ => dataBuilder.Options)); } ``` snippet source | anchor Then use the same identifier for recording: ```cs var httpClient = factory.CreateClient(); Recording.Start(testName); var companies = await httpClient.GetFromJsonAsync("/companies"); var entries = Recording.Stop(testName); ``` snippet source | anchor The results will not be automatically included in verified file so it will have to be verified manually: ```cs await Verify( new { target = companies!.Length, sql = entries }); ``` snippet source | anchor ## ScrubInlineEfDateTimes In some scenarios EntityFrmaeowrk does not parameterise DateTimes. For example when querying [temporal tables](https://learn.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables). `ScrubInlineEfDateTimes()` is a convenience method that calls `.ScrubInlineDateTimes("yyyy-MM-ddTHH:mm:ss.fffffffZ")`. ### Static usage ``` VerifyEntityFramework.ScrubInlineEfDateTimes(); ``` ### Instance usage ```cs var settings = new VerifySettings(); settings.ScrubInlineEfDateTimes(); await Verify(target, settings); ``` snippet source | anchor ### Fluent usage ```cs await Verify(target) .ScrubInlineEfDateTimes(); ``` snippet source | anchor ## Icon [Database](https://thenounproject.com/term/database/310841/) designed by [Creative Stall](https://thenounproject.com/creativestall/) from [The Noun Project](https://thenounproject.com).