# Verify.EntityFramework [![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)](https://www.nuget.org/packages/Verify.EntityFramework/) [![NuGet Status](https://img.shields.io/nuget/v/Verify.EntityFrameworkClassic.svg)](https://www.nuget.org/packages/Verify.EntityFrameworkClassic/) Extends [Verify](https://github.com/VerifyTests/Verify) to allow verification of EntityFramework bits. Support is available via a [Tidelift Subscription](https://tidelift.com/subscription/pkg/nuget-verify?utm_source=nuget-verify&utm_medium=referral&utm_campaign=enterprise).
Part of the .NET Foundation ## Contents * [Enable](#enable) * [EF Core](#ef-core) * [EF Classic](#ef-classic) * [Recording](#recording) * [Enable](#enable-1) * [Usage](#usage) * [DbContext spanning](#dbcontext-spanning) * [ChangeTracking](#changetracking) * [Added entity](#added-entity) * [Deleted entity](#deleted-entity) * [Modified entity](#modified-entity) * [Queryable](#queryable) * [EF Core](#ef-core-1) * [EF Classic](#ef-classic-1) * [AllData](#alldata) * [Security contact information](#security-contact-information) ## NuGet package * https://nuget.org/packages/Verify.EntityFramework/ * https://nuget.org/packages/Verify.EntityFrameworkClassic/ ## Enable Enable VerifyEntityFramewok once at assembly load time: ### EF Core ```cs VerifyEntityFramework.Enable(); ``` snippet source | anchor ### EF Classic ```cs VerifyEntityFrameworkClassic.Enable(); ``` snippet source | anchor ## Recording Recording allows all commands executed by EF to be captured and then (optionally) verified. ### Enable Call `SqlRecording.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 On the `DbContext` call `SqlRecording.StartRecording()` to start recording. ```cs var company = new Company { Content = "Title" }; data.Add(company); await data.SaveChangesAsync(); SqlRecording.StartRecording(); await data.Companies .Where(x => x.Content == "Title") .ToListAsync(); await Verifier.Verify(data.Companies.Count()); ``` snippet source | anchor Will result in the following verified file: ```txt { target: '5', sql: [ { Type: 'ReaderExecutedAsync', Text: "SELECT [c].[Id], [c].[Content] FROM [Companies] AS [c] WHERE [c].[Content] = N'Title'" }, { Type: 'ReaderExecuted', Text: 'SELECT COUNT(*) FROM [Companies] AS [c]' } ] } ``` 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); SqlRecording.StartRecording(); var company = new Company { Content = "Title" }; data1.Add(company); await data1.SaveChangesAsync(); await using var data2 = new SampleDbContext(builder.Options); await data2.Companies .Where(x => x.Content == "Title") .ToListAsync(); await Verifier.Verify(data2.Companies.Count()); ``` snippet source | anchor ```txt { target: '5', sql: [ { Type: 'ReaderExecutedAsync', HasTransaction: true, Parameters: { @p0: 0, @p1: 'Title' }, Text: 'SET NOCOUNT ON; INSERT INTO [Companies] ([Id], [Content]) VALUES (@p0, @p1);' }, { Type: 'ReaderExecutedAsync', Text: "SELECT [c].[Id], [c].[Content] FROM [Companies] AS [c] WHERE [c].[Content] = N'Title'" }, { Type: 'ReaderExecuted', Text: 'SELECT COUNT(*) FROM [Companies] AS [c]' } ] } ``` 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 { Content = "before" }; data.Add(company); await Verifier.Verify(data.ChangeTracker); } ``` snippet source | anchor Will result in the following verified file: ```txt { Added: { Company: { Id: 0, Content: 'before' } } } ``` 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 {Content = "before"}); await data.SaveChangesAsync(); var company = data.Companies.Single(); data.Companies.Remove(company); await Verifier.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 { Content = "before" }; data.Add(company); await data.SaveChangesAsync(); data.Companies.Single().Content = "after"; await Verifier.Verify(data.ChangeTracker); } ``` snippet source | anchor Will result in the following verified file: ```txt { Modified: { Company: { Id: 0, Content: { Original: 'before', Current: 'after' } } } } ``` snippet source | anchor ## Queryable This test: ```cs var queryable = data.Companies .Where(x => x.Content == "value"); await Verifier.Verify(queryable); ``` snippet source | anchor Will result in the following verified file: ### EF Core ```txt SELECT [c].[Id], [c].[Content] FROM [Companies] AS [c] WHERE [c].[Content] = N'value' ``` snippet source | anchor ### EF Classic ```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 var settings = new VerifySettings(); settings.ModifySerialization( serialization => serialization.AddExtraSettings( serializer => serializer.TypeNameHandling = TypeNameHandling.Objects)); await Verifier.Verify(data.AllData(), settings); ``` snippet source | anchor Will result in the following verified file with all data in the database: ```txt [ { $type: 'Company', Id: 1, Content: 'Company1' }, { $type: 'Company', Id: 4, Content: 'Company2' }, { $type: 'Company', Id: 6, Content: 'Company3' }, { $type: 'Company', Id: 7, Content: 'Company4' }, { $type: 'Employee', Id: 2, CompanyId: 1, Content: 'Employee1', Age: 25 }, { $type: 'Employee', Id: 3, CompanyId: 1, Content: 'Employee2', Age: 31 }, { $type: 'Employee', Id: 5, CompanyId: 4, Content: 'Employee4', Age: 34 } ] ``` snippet source | anchor ## Security contact information To report a security vulnerability, use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. ## Icon [Database](https://thenounproject.com/term/database/310841/) designed by [Creative Stall](https://thenounproject.com/creativestall/) from [The Noun Project](https://thenounproject.com/creativepriyanka).