From fc017f031a770a556eaf8e84e32ee90cafb33dc8 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 19 Dec 2022 09:35:28 +1100 Subject: [PATCH 1/3] . --- readme.md | 47 +++++++++++------- ...eNavigationPropertiesExplicit.verified.txt | 3 ++ src/Verify.EntityFramework.Tests/CoreTests.cs | 49 ++++++++++++++++--- .../ModuleInitializer.cs | 15 +++++- .../Converters/TrackerConverter.cs | 2 +- .../VerifyEntityFramework.cs | 35 ++++++++++--- .../ClassicTests.cs | 2 +- .../Converters/TrackerConverter.cs | 6 +-- 8 files changed, 120 insertions(+), 39 deletions(-) create mode 100644 src/Verify.EntityFramework.Tests/CoreTests.IgnoreNavigationPropertiesExplicit.verified.txt diff --git a/readme.md b/readme.md index fe43b54b..ef19bbe5 100644 --- a/readme.md +++ b/readme.md @@ -23,14 +23,25 @@ Enable VerifyEntityFramework once at assembly load time: ```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() { - VerifyEntityFramework.Enable(); + var model = GetDbModel(); + VerifyEntityFramework.Enable(model); ``` -snippet source | anchor +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 @@ -68,7 +79,7 @@ builder.UseSqlServer(connection); builder.EnableRecording(); var data = new SampleDbContext(builder.Options); ``` -snippet source | anchor +snippet source | anchor `EnableRecording` should only be called in the test context. @@ -91,12 +102,12 @@ await data.SaveChangesAsync(); EfRecording.StartRecording(); await data.Companies - .Where(x => x.Content == "Title") + .Where(_ => _.Content == "Title") .ToListAsync(); await Verify(data.Companies.Count()); ``` -snippet source | anchor +snippet source | anchor Will result in the following verified file: @@ -144,7 +155,7 @@ await data.SaveChangesAsync(); EfRecording.StartRecording(); await data.Companies - .Where(x => x.Content == "Title") + .Where(_ => _.Content == "Title") .ToListAsync(); var entries = EfRecording.FinishRecording(); @@ -155,7 +166,7 @@ await Verify(new sql = entries }); ``` -snippet source | anchor +snippet source | anchor @@ -181,12 +192,12 @@ await data1.SaveChangesAsync(); await using var data2 = new SampleDbContext(builder.Options); await data2.Companies - .Where(x => x.Content == "Title") + .Where(_ => _.Content == "Title") .ToListAsync(); await Verify(data2.Companies.Count()); ``` -snippet source | anchor +snippet source | anchor @@ -377,10 +388,10 @@ This test: ```cs var queryable = data.Companies - .Where(x => x.Content == "value"); + .Where(_ => _.Content == "value"); await Verify(queryable); ``` -snippet source | anchor +snippet source | anchor Will result in the following verified file: @@ -426,7 +437,7 @@ await Verify(data.AllData()) serializer => serializer.TypeNameHandling = TypeNameHandling.Objects); ``` -snippet source | anchor +snippet source | anchor Will result in the following verified file with all data in the database: @@ -506,7 +517,7 @@ public async Task IgnoreNavigationProperties() Company = company }; await Verify(employee) - .IgnoreNavigationProperties(data); + .IgnoreNavigationProperties(); } ``` snippet source | anchor @@ -520,9 +531,9 @@ public async Task IgnoreNavigationProperties() ```cs var options = DbContextOptions(); using var data = new SampleDbContext(options); -VerifyEntityFramework.IgnoreNavigationProperties(data.Model); +VerifyEntityFramework.IgnoreNavigationProperties(); ``` -snippet source | anchor +snippet source | anchor @@ -542,7 +553,7 @@ To be able to use [WebApplicationFactory](https://docs.microsoft.com/en-us/dotne .Options); }); ``` -snippet source | anchor +snippet source | anchor Then use the same identifier for recording: @@ -558,7 +569,7 @@ var companies = await httpClient.GetFromJsonAsync("/companies"); var entries = EfRecording.FinishRecording(testName); ``` -snippet source | anchor +snippet source | anchor The results will not be automatically included in verified file so it will have to be verified manually: @@ -572,7 +583,7 @@ await Verify(new sql = entries }); ``` -snippet source | anchor +snippet source | anchor diff --git a/src/Verify.EntityFramework.Tests/CoreTests.IgnoreNavigationPropertiesExplicit.verified.txt b/src/Verify.EntityFramework.Tests/CoreTests.IgnoreNavigationPropertiesExplicit.verified.txt new file mode 100644 index 00000000..9a70a7ad --- /dev/null +++ b/src/Verify.EntityFramework.Tests/CoreTests.IgnoreNavigationPropertiesExplicit.verified.txt @@ -0,0 +1,3 @@ +{ + Content: employee +} \ No newline at end of file diff --git a/src/Verify.EntityFramework.Tests/CoreTests.cs b/src/Verify.EntityFramework.Tests/CoreTests.cs index ee84c1ad..39d6b7dc 100644 --- a/src/Verify.EntityFramework.Tests/CoreTests.cs +++ b/src/Verify.EntityFramework.Tests/CoreTests.cs @@ -71,6 +71,30 @@ public async Task IgnoreNavigationProperties() await using var data = new SampleDbContext(options); + var company = new Company + { + Content = "company" + }; + var employee = new Employee + { + Content = "employee", + Company = company + }; + await Verify(employee) + .IgnoreNavigationProperties(); + } + + #endregion + + #region IgnoreNavigationPropertiesExplicit + + [Test] + public async Task IgnoreNavigationPropertiesExplicit() + { + var options = DbContextOptions(); + + await using var data = new SampleDbContext(options); + var company = new Company { Content = "company" @@ -90,6 +114,17 @@ void IgnoreNavigationPropertiesGlobal() { #region IgnoreNavigationPropertiesGlobal + var options = DbContextOptions(); + using var data = new SampleDbContext(options); + VerifyEntityFramework.IgnoreNavigationProperties(); + + #endregion + } + + void IgnoreNavigationPropertiesGlobalExplicit() + { + #region IgnoreNavigationPropertiesGlobalExplicit + var options = DbContextOptions(); using var data = new SampleDbContext(options); VerifyEntityFramework.IgnoreNavigationProperties(data.Model); @@ -213,7 +248,7 @@ public async Task Queryable() #region Queryable var queryable = data.Companies - .Where(x => x.Content == "value"); + .Where(_ => _.Content == "value"); await Verify(queryable); #endregion @@ -226,7 +261,7 @@ public async Task SetSelect() var data = database.Context; var query = data.Set() - .Select(x => x.Id); + .Select(_ => _.Id); await Verify(query); } @@ -236,7 +271,7 @@ public async Task NestedQueryable() var database = await DbContextBuilder.GetDatabase("NestedQueryable"); var data = database.Context; var queryable = data.Companies - .Where(x => x.Content == "value"); + .Where(_ => _.Content == "value"); await Verify(queryable); } @@ -286,7 +321,7 @@ public async Task MultiRecording() { var s = i.ToString(); await data.Companies - .Where(x => x.Content == s) + .Where(_ => _.Content == s) .ToListAsync(); } @@ -325,7 +360,7 @@ public async Task MultiDbContexts() await using var data2 = new SampleDbContext(builder.Options); await data2.Companies - .Where(x => x.Content == "Title") + .Where(_ => _.Content == "Title") .ToListAsync(); await Verify(data2.Companies.Count()); @@ -351,7 +386,7 @@ public async Task Recording() EfRecording.StartRecording(); await data.Companies - .Where(x => x.Content == "Title") + .Where(_ => _.Content == "Title") .ToListAsync(); await Verify(data.Companies.Count()); @@ -475,7 +510,7 @@ public async Task RecordingSpecific() EfRecording.StartRecording(); await data.Companies - .Where(x => x.Content == "Title") + .Where(_ => _.Content == "Title") .ToListAsync(); var entries = EfRecording.FinishRecording(); diff --git a/src/Verify.EntityFramework.Tests/ModuleInitializer.cs b/src/Verify.EntityFramework.Tests/ModuleInitializer.cs index 60b27cdb..c7889810 100644 --- a/src/Verify.EntityFramework.Tests/ModuleInitializer.cs +++ b/src/Verify.EntityFramework.Tests/ModuleInitializer.cs @@ -1,11 +1,22 @@ -public static class ModuleInitializer +using Microsoft.EntityFrameworkCore.Metadata; + +public static class ModuleInitializer { #region EnableCore + 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() { - VerifyEntityFramework.Enable(); + var model = GetDbModel(); + VerifyEntityFramework.Enable(model); #endregion diff --git a/src/Verify.EntityFramework/Converters/TrackerConverter.cs b/src/Verify.EntityFramework/Converters/TrackerConverter.cs index 7172c8a7..8abe492f 100644 --- a/src/Verify.EntityFramework/Converters/TrackerConverter.cs +++ b/src/Verify.EntityFramework/Converters/TrackerConverter.cs @@ -39,7 +39,7 @@ static void HandleDeleted(List entries, VerifyJsonWriter writer) static void HandleAdded(List entries, VerifyJsonWriter writer) { var added = entries - .Where(x => x.State == EntityState.Added) + .Where(_ => _.State == EntityState.Added) .ToList(); if (!added.Any()) { diff --git a/src/Verify.EntityFramework/VerifyEntityFramework.cs b/src/Verify.EntityFramework/VerifyEntityFramework.cs index 358e81c6..808b8444 100644 --- a/src/Verify.EntityFramework/VerifyEntityFramework.cs +++ b/src/Verify.EntityFramework/VerifyEntityFramework.cs @@ -2,6 +2,8 @@ public static class VerifyEntityFramework { + static IModel? model; + public static async IAsyncEnumerable AllData(this DbContext data) { foreach (var entityType in data @@ -25,9 +27,9 @@ public static void IgnoreNavigationProperties(this VerifySettings settings, DbCo public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings, DbContext context) => settings.IgnoreNavigationProperties(context.Model); - public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings, IModel model) + public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings, IModel? model = null) { - foreach (var (type, name) in model.GetNavigations()) + foreach (var (type, name) in GetModel(model).GetNavigations()) { settings.IgnoreMember(type, name); } @@ -35,17 +37,32 @@ public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings return settings; } - public static void IgnoreNavigationProperties(this VerifySettings settings, IModel model) + public static void IgnoreNavigationProperties(this VerifySettings settings, IModel? model = null) { - foreach (var (type, name) in model.GetNavigations()) + foreach (var (type, name) in GetModel(model).GetNavigations()) { settings.IgnoreMember(type, name); } } - public static void IgnoreNavigationProperties(IModel model) + static IModel GetModel(IModel? model) { - foreach (var (type, name) in model.GetNavigations()) + if (model != null) + { + return model; + } + + if (VerifyEntityFramework.model == null) + { + throw new("The `model` parameter must be provided wither on this method or on VerifyEntityFramework.Enable()"); + } + + return VerifyEntityFramework.model; + } + + public static void IgnoreNavigationProperties(IModel? model = null) + { + foreach (var (type, name) in GetModel(model).GetNavigations()) { VerifierSettings.IgnoreMember(type, name); } @@ -62,8 +79,12 @@ public static void IgnoreNavigationProperties(IModel model) } } - public static void Enable() + public static void Enable(DbContext context) => + Enable(context.Model); + + public static void Enable(IModel? model = null) { + VerifyEntityFramework.model = model; VerifierSettings.RegisterJsonAppender(_ => { var entries = LogCommandInterceptor.Stop(); diff --git a/src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs b/src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs index 56077df3..cacb4c36 100644 --- a/src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs +++ b/src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs @@ -119,7 +119,7 @@ public async Task Queryable() { var database = await DbContextBuilder.GetDatabase("Queryable"); var data = database.Context; - var queryable = data.Companies.Where(x => x.Content == "value"); + var queryable = data.Companies.Where(_ => _.Content == "value"); await Verify(queryable); } diff --git a/src/Verify.EntityFrameworkClassic/Converters/TrackerConverter.cs b/src/Verify.EntityFrameworkClassic/Converters/TrackerConverter.cs index 68a56004..45bce266 100644 --- a/src/Verify.EntityFrameworkClassic/Converters/TrackerConverter.cs +++ b/src/Verify.EntityFrameworkClassic/Converters/TrackerConverter.cs @@ -31,7 +31,7 @@ public override void Write(VerifyJsonWriter writer, DbChangeTracker tracker) static void HandleDeleted(List entries, VerifyJsonWriter writer, DbContext data) { var deleted = entries - .Where(x => x.State == EntityState.Deleted) + .Where(_ => _.State == EntityState.Deleted) .ToList(); if (!deleted.Any()) { @@ -54,7 +54,7 @@ static void HandleDeleted(List entries, VerifyJsonWriter writer, static void HandleAdded(List entries, VerifyJsonWriter writer) { var added = entries - .Where(x => x.State == EntityState.Added) + .Where(_ => _.State == EntityState.Added) .ToList(); if (!added.Any()) { @@ -83,7 +83,7 @@ static void HandleAdded(List entries, VerifyJsonWriter writer) static void HandleModified(List entries, VerifyJsonWriter writer, DbContext context) { var modified = entries - .Where(x => x.State == EntityState.Modified) + .Where(_ => _.State == EntityState.Modified) .ToList(); if (!modified.Any()) { From ee1e98d787b0abe276e490801a10ad35e6415464 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 19 Dec 2022 09:41:17 +1100 Subject: [PATCH 2/3] . --- .../Snippets/DataContext/SampleDbContext.cs | 6 +-- .../VerifyEntityFramework.cs | 38 ++++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Verify.EntityFramework.Tests/Snippets/DataContext/SampleDbContext.cs b/src/Verify.EntityFramework.Tests/Snippets/DataContext/SampleDbContext.cs index 34c83d7a..47f07c40 100644 --- a/src/Verify.EntityFramework.Tests/Snippets/DataContext/SampleDbContext.cs +++ b/src/Verify.EntityFramework.Tests/Snippets/DataContext/SampleDbContext.cs @@ -9,12 +9,12 @@ public SampleDbContext(DbContextOptions options) : { } - protected override void OnModelCreating(ModelBuilder modelBuilder) + protected override void OnModelCreating(ModelBuilder builder) { - modelBuilder.Entity() + builder.Entity() .HasMany(c => c.Employees) .WithOne(e => e.Company) .IsRequired(); - modelBuilder.Entity(); + builder.Entity(); } } \ No newline at end of file diff --git a/src/Verify.EntityFramework/VerifyEntityFramework.cs b/src/Verify.EntityFramework/VerifyEntityFramework.cs index 808b8444..d3f8b75c 100644 --- a/src/Verify.EntityFramework/VerifyEntityFramework.cs +++ b/src/Verify.EntityFramework/VerifyEntityFramework.cs @@ -2,7 +2,7 @@ public static class VerifyEntityFramework { - static IModel? model; + static List<(Type type, string name)>? modelNavigations; public static async IAsyncEnumerable AllData(this DbContext data) { @@ -29,7 +29,7 @@ public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings, IModel? model = null) { - foreach (var (type, name) in GetModel(model).GetNavigations()) + foreach (var (type, name) in model.GetNavigationsOrShared()) { settings.IgnoreMember(type, name); } @@ -39,33 +39,33 @@ public static SettingsTask IgnoreNavigationProperties(this SettingsTask settings public static void IgnoreNavigationProperties(this VerifySettings settings, IModel? model = null) { - foreach (var (type, name) in GetModel(model).GetNavigations()) + foreach (var (type, name) in model.GetNavigationsOrShared()) { settings.IgnoreMember(type, name); } } - static IModel GetModel(IModel? model) + public static void IgnoreNavigationProperties(IModel? model = null) { - if (model != null) - { - return model; - } - - if (VerifyEntityFramework.model == null) + foreach (var (type, name) in model.GetNavigationsOrShared()) { - throw new("The `model` parameter must be provided wither on this method or on VerifyEntityFramework.Enable()"); + VerifierSettings.IgnoreMember(type, name); } - - return VerifyEntityFramework.model; } - public static void IgnoreNavigationProperties(IModel? model = null) + static IEnumerable<(Type type, string name)> GetNavigationsOrShared(this IModel? model) { - foreach (var (type, name) in GetModel(model).GetNavigations()) + if (model == null) { - VerifierSettings.IgnoreMember(type, name); + if (modelNavigations != null) + { + return modelNavigations; + } + + throw new("The `model` parameter must be provided wither on this method or on VerifyEntityFramework.Enable()"); } + + return GetNavigations(model); } static IEnumerable<(Type type, string name)> GetNavigations(this IModel model) @@ -84,7 +84,11 @@ public static void Enable(DbContext context) => public static void Enable(IModel? model = null) { - VerifyEntityFramework.model = model; + if (model != null) + { + modelNavigations = model.GetNavigations().ToList(); + } + VerifierSettings.RegisterJsonAppender(_ => { var entries = LogCommandInterceptor.Stop(); From a74d105b0c22a3b69ebdde9f321562990062b55b Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 19 Dec 2022 09:43:03 +1100 Subject: [PATCH 3/3] . --- src/Directory.Build.props | 2 +- src/Verify.EntityFramework.Tests/CoreTests.cs | 8 ++++---- src/Verify.EntityFramework/LogCommandInterceptor.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 12cb5270..62cc5510 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ CS1591;CS0649;CS8632;EF1001 - 9.2.0 + 9.3.0 1.0.0 EntityFramework, Verify Extends Verify (https://github.com/VerifyTests/Verify) to allow verification of EntityFramework bits. diff --git a/src/Verify.EntityFramework.Tests/CoreTests.cs b/src/Verify.EntityFramework.Tests/CoreTests.cs index 39d6b7dc..6ad97452 100644 --- a/src/Verify.EntityFramework.Tests/CoreTests.cs +++ b/src/Verify.EntityFramework.Tests/CoreTests.cs @@ -403,7 +403,7 @@ public async Task RecordingWebApplicationFactory(int run) // Not actually the test name, the variable name is for README.md to make sense var testName = nameof(RecordingWebApplicationFactory) + run; - using var connection = new SqliteConnection($"Data Source={testName};Mode=Memory;Cache=Shared"); + await using var connection = new SqliteConnection($"Data Source={testName};Mode=Memory;Cache=Shared"); await connection.OpenAsync(); var factory = new CustomWebApplicationFactory(testName); @@ -448,7 +448,7 @@ await Verify(new class CustomWebApplicationFactory : WebApplicationFactory { - readonly string testName; + string testName; public CustomWebApplicationFactory(string testName) => this.testName = testName; @@ -471,7 +471,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) => protected override IHostBuilder CreateHostBuilder() => Host.CreateDefaultBuilder() - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + .ConfigureWebHostDefaults(builder => builder.UseStartup()); } public class Startup @@ -488,7 +488,7 @@ public void Configure(IApplicationBuilder app) app.UseRouting(); app.UseEndpoints(endpoints - => endpoints.MapGet("/companies", async (SampleDbContext data) => await data.Companies.ToListAsync())); + => endpoints.MapGet("/companies", (SampleDbContext data) => data.Companies.ToListAsync())); } } diff --git a/src/Verify.EntityFramework/LogCommandInterceptor.cs b/src/Verify.EntityFramework/LogCommandInterceptor.cs index 130ee26e..3c6ab233 100644 --- a/src/Verify.EntityFramework/LogCommandInterceptor.cs +++ b/src/Verify.EntityFramework/LogCommandInterceptor.cs @@ -3,7 +3,7 @@ class LogCommandInterceptor : { static AsyncLocal asyncLocal = new(); static ConcurrentDictionary> namedEvents = new(StringComparer.OrdinalIgnoreCase); - readonly string? identifier; + string? identifier; public static void Start() => asyncLocal.Value = new(); public static void Start(string identifier) => namedEvents.GetOrAdd(identifier, _ => new());