diff --git a/readme.md b/readme.md index fe43b54..ef19bbe 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/Directory.Build.props b/src/Directory.Build.props index 12cb527..62cc551 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.IgnoreNavigationPropertiesExplicit.verified.txt b/src/Verify.EntityFramework.Tests/CoreTests.IgnoreNavigationPropertiesExplicit.verified.txt new file mode 100644 index 0000000..9a70a7a --- /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 ee84c1a..6ad9745 100644 --- a/src/Verify.EntityFramework.Tests/CoreTests.cs +++ b/src/Verify.EntityFramework.Tests/CoreTests.cs @@ -71,6 +71,30 @@ public class CoreTests 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 @@ public class CoreTests { #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 class CoreTests #region Queryable var queryable = data.Companies - .Where(x => x.Content == "value"); + .Where(_ => _.Content == "value"); await Verify(queryable); #endregion @@ -226,7 +261,7 @@ public class CoreTests var data = database.Context; var query = data.Set() - .Select(x => x.Id); + .Select(_ => _.Id); await Verify(query); } @@ -236,7 +271,7 @@ public class CoreTests 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 class CoreTests { var s = i.ToString(); await data.Companies - .Where(x => x.Content == s) + .Where(_ => _.Content == s) .ToListAsync(); } @@ -325,7 +360,7 @@ public class CoreTests 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 class CoreTests EfRecording.StartRecording(); await data.Companies - .Where(x => x.Content == "Title") + .Where(_ => _.Content == "Title") .ToListAsync(); await Verify(data.Companies.Count()); @@ -368,7 +403,7 @@ public class CoreTests // 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); @@ -413,7 +448,7 @@ public class CoreTests class CustomWebApplicationFactory : WebApplicationFactory { - readonly string testName; + string testName; public CustomWebApplicationFactory(string testName) => this.testName = testName; @@ -436,7 +471,7 @@ public class CoreTests protected override IHostBuilder CreateHostBuilder() => Host.CreateDefaultBuilder() - .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); + .ConfigureWebHostDefaults(builder => builder.UseStartup()); } public class Startup @@ -453,7 +488,7 @@ public class CoreTests app.UseRouting(); app.UseEndpoints(endpoints - => endpoints.MapGet("/companies", async (SampleDbContext data) => await data.Companies.ToListAsync())); + => endpoints.MapGet("/companies", (SampleDbContext data) => data.Companies.ToListAsync())); } } @@ -475,7 +510,7 @@ public class CoreTests 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 60b27cd..c788981 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.Tests/Snippets/DataContext/SampleDbContext.cs b/src/Verify.EntityFramework.Tests/Snippets/DataContext/SampleDbContext.cs index 34c83d7..47f07c4 100644 --- a/src/Verify.EntityFramework.Tests/Snippets/DataContext/SampleDbContext.cs +++ b/src/Verify.EntityFramework.Tests/Snippets/DataContext/SampleDbContext.cs @@ -9,12 +9,12 @@ { } - 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/Converters/TrackerConverter.cs b/src/Verify.EntityFramework/Converters/TrackerConverter.cs index 7172c8a..8abe492 100644 --- a/src/Verify.EntityFramework/Converters/TrackerConverter.cs +++ b/src/Verify.EntityFramework/Converters/TrackerConverter.cs @@ -39,7 +39,7 @@ 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/LogCommandInterceptor.cs b/src/Verify.EntityFramework/LogCommandInterceptor.cs index 130ee26..3c6ab23 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()); diff --git a/src/Verify.EntityFramework/VerifyEntityFramework.cs b/src/Verify.EntityFramework/VerifyEntityFramework.cs index 358e81c..d3f8b75 100644 --- a/src/Verify.EntityFramework/VerifyEntityFramework.cs +++ b/src/Verify.EntityFramework/VerifyEntityFramework.cs @@ -2,6 +2,8 @@ public static class VerifyEntityFramework { + static List<(Type type, string name)>? modelNavigations; + public static async IAsyncEnumerable AllData(this DbContext data) { foreach (var entityType in data @@ -25,9 +27,9 @@ public static class VerifyEntityFramework 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 model.GetNavigationsOrShared()) { settings.IgnoreMember(type, name); } @@ -35,22 +37,37 @@ public static class VerifyEntityFramework 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 model.GetNavigationsOrShared()) { settings.IgnoreMember(type, name); } } - public static void IgnoreNavigationProperties(IModel model) + public static void IgnoreNavigationProperties(IModel? model = null) { - foreach (var (type, name) in model.GetNavigations()) + foreach (var (type, name) in model.GetNavigationsOrShared()) { VerifierSettings.IgnoreMember(type, name); } } + static IEnumerable<(Type type, string name)> GetNavigationsOrShared(this IModel? model) + { + if (model == null) + { + 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) { foreach (var type in model.GetEntityTypes()) @@ -62,8 +79,16 @@ public static class VerifyEntityFramework } } - public static void Enable() + public static void Enable(DbContext context) => + Enable(context.Model); + + public static void Enable(IModel? model = null) { + if (model != null) + { + modelNavigations = model.GetNavigations().ToList(); + } + VerifierSettings.RegisterJsonAppender(_ => { var entries = LogCommandInterceptor.Stop(); diff --git a/src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs b/src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs index 56077df..cacb4c3 100644 --- a/src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs +++ b/src/Verify.EntityFrameworkClassic.Tests/ClassicTests.cs @@ -119,7 +119,7 @@ public class ClassicTests { 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 68a5600..45bce26 100644 --- a/src/Verify.EntityFrameworkClassic/Converters/TrackerConverter.cs +++ b/src/Verify.EntityFrameworkClassic/Converters/TrackerConverter.cs @@ -31,7 +31,7 @@ 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 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 HandleModified(List entries, VerifyJsonWriter writer, DbContext context) { var modified = entries - .Where(x => x.State == EntityState.Modified) + .Where(_ => _.State == EntityState.Modified) .ToList(); if (!modified.Any()) {