diff --git a/Directory.Packages.props b/Directory.Packages.props index 8f791b37..fd58cef0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -28,7 +28,6 @@ - @@ -38,6 +37,14 @@ + + + + + + + + diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/Ardalis.Specification.EntityFramework6.Tests.csproj b/tests/Ardalis.Specification.EntityFramework6.Tests/Ardalis.Specification.EntityFramework6.Tests.csproj index 899b5972..c80c20c6 100644 --- a/tests/Ardalis.Specification.EntityFramework6.Tests/Ardalis.Specification.EntityFramework6.Tests.csproj +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/Ardalis.Specification.EntityFramework6.Tests.csproj @@ -7,11 +7,12 @@ - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Address.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Address.cs new file mode 100644 index 00000000..e644cac6 --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Address.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Tests.FixtureNew; + +public record Address +{ + public int Id { get; set; } + public string? Street { get; set; } + + public Store Store { get; set; } = default!; +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Company.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Company.cs new file mode 100644 index 00000000..b78353b0 --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Company.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Tests.FixtureNew; + +public record Company +{ + public int Id { get; set; } + public required string Name { get; set; } + + public int CountryId { get; set; } + public Country Country { get; set; } = default!; + + public List Stores { get; set; } = []; +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Country.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Country.cs new file mode 100644 index 00000000..c445a3b2 --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Country.cs @@ -0,0 +1,9 @@ +namespace Tests.FixtureNew; + +public record Country +{ + public int Id { get; set; } + public int No { get; set; } + public string? Name { get; set; } + public bool IsDeleted { get; set; } +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Product.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Product.cs new file mode 100644 index 00000000..f256fa50 --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Product.cs @@ -0,0 +1,12 @@ +namespace Tests.FixtureNew; + +public record Product +{ + public int Id { get; set; } + public string? Name { get; set; } + + public int StoreId { get; set; } + public Store Store { get; set; } = default!; + + public List? Images { get; set; } +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/ProductImage.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/ProductImage.cs new file mode 100644 index 00000000..479a8e6c --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/ProductImage.cs @@ -0,0 +1,9 @@ +namespace Tests.FixtureNew; + +public record ProductImage +{ + public int Id { get; set; } + public string? ImageUrl { get; set; } + public int ProductId { get; set; } + public Product Product { get; set; } = default!; +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Store.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Store.cs new file mode 100644 index 00000000..5e46502a --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Data/Store.cs @@ -0,0 +1,15 @@ +namespace Tests.FixtureNew; + +public record Store +{ + public int Id { get; set; } + public string? Name { get; set; } + public string? City { get; set; } + + public int CompanyId { get; set; } + public Company Company { get; set; } = default!; + + public Address Address { get; set; } = default!; + + public List Products { get; set; } = []; +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/IntegrationTest.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/IntegrationTest.cs new file mode 100644 index 00000000..e669deb8 --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/IntegrationTest.cs @@ -0,0 +1,50 @@ +using System.Collections; + +namespace Tests.FixtureNew; + +public class IntegrationTest : IAsyncLifetime +{ + protected TestDbContext DbContext { get; private set; } = default!; + private readonly TestFactory _testFactory; + + public IntegrationTest(TestFactory testFactory) + { + _testFactory = testFactory; + } + + public Task InitializeAsync() + { + DbContext = new TestDbContext(_testFactory.ConnectionString); + return Task.CompletedTask; + } + + public async Task DisposeAsync() + { + DbContext.Dispose(); + await _testFactory.ResetDatabase(); + } + + public async Task SeedAsync(TEntity entity) where TEntity : class + { + using var dbContext = new TestDbContext(_testFactory.ConnectionString); + dbContext.Set().Add(entity); + await dbContext.SaveChangesAsync(); + } + + public async Task SeedRangeAsync(TEntity[] entities) where TEntity : class + { + using var dbContext = new TestDbContext(_testFactory.ConnectionString); + dbContext.Set().AddRange(entities); + await dbContext.SaveChangesAsync(); + } + + public async Task SeedRangeAsync(IEnumerable entities) + { + using var dbContext = new TestDbContext(_testFactory.ConnectionString); + foreach (var entity in entities) + { + dbContext.Set(entity.GetType()).Add(entity); + } + await dbContext.SaveChangesAsync(); + } +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Repository.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Repository.cs new file mode 100644 index 00000000..8f01119c --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/Repository.cs @@ -0,0 +1,5 @@ +namespace Tests.FixtureNew; + +public class Repository(TestDbContext context) : RepositoryBase(context) where T : class +{ +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/SharedCollection.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/SharedCollection.cs new file mode 100644 index 00000000..ae75ad31 --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/SharedCollection.cs @@ -0,0 +1,6 @@ +namespace Tests.FixtureNew; + +[CollectionDefinition("SharedCollection")] +public class SharedCollection : ICollectionFixture +{ +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/TestDbContext.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/TestDbContext.cs new file mode 100644 index 00000000..0c9aef4d --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/TestDbContext.cs @@ -0,0 +1,31 @@ +using System.Data.Entity; + +namespace Tests.FixtureNew; + +public class TestDbContext : DbContext +{ + public TestDbContext(string connectionString) : base(connectionString) + { + } + + public DbSet Countries => Set(); + public DbSet Companies => Set(); + public DbSet Stores => Set(); + public DbSet
Addresses => Set
(); + public DbSet Products => Set(); + + protected override void OnModelCreating(DbModelBuilder modelBuilder) + { + // Store-Address one-to-one (Address has StoreId FK) + modelBuilder.Entity() + .HasOptional(s => s.Address) + .WithRequired(a => a.Store) + .Map(m => m.MapKey("StoreId")); + + // Country-Company one-to-many (Company has CountryId FK) + modelBuilder.Entity() + .HasRequired(co => co.Country) + .WithMany() + .HasForeignKey(co => co.CountryId); + } +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/TestFactory.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/TestFactory.cs new file mode 100644 index 00000000..196e6dc9 --- /dev/null +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/FixtureNew/TestFactory.cs @@ -0,0 +1,89 @@ +using MartinCostello.SqlLocalDb; +using Testcontainers.MsSql; +using Respawn; + +namespace Tests.FixtureNew; + +public class TestFactory : IAsyncLifetime +{ + // Flag to force using Docker SQL Server. Update it manually if you want to avoid localDb locally. + private const bool FORCE_DOCKER = false; + + private string _connectionString = default!; + private MsSqlContainer? _dbContainer = null; + + public string ConnectionString => _connectionString; + public TestDbContext DbContext => new TestDbContext(_connectionString); + +#if NET9_0_OR_GREATER + private Respawner _respawner = default!; + public async Task ResetDatabase() => await _respawner.ResetAsync(_connectionString); +#elif NET472 + private Checkpoint _respawner = default!; + public Task ResetDatabase() => _respawner.Reset(_connectionString); +#endif + + public async Task InitializeAsync() + { + using (var localDB = new SqlLocalDbApi()) + { + if (FORCE_DOCKER || !localDB.IsLocalDBInstalled()) + { + _dbContainer = CreateContainer(); + await _dbContainer.StartAsync(); + _connectionString = _dbContainer.GetConnectionString(); + } + else + { +#if NET9_0_OR_GREATER + _connectionString = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SpecificationTestsDB_EF6_NET9;Integrated Security=SSPI;TrustServerCertificate=True;"; +#elif NET472 + + _connectionString = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SpecificationTestsDB_EF6_NETFFX;Integrated Security=SSPI;TrustServerCertificate=True;"; +#endif + } + } + + Console.WriteLine($"Connection string: {_connectionString}"); + + using (var dbContext = new TestDbContext(_connectionString)) + { + //dbContext.Database.Delete(); + dbContext.Database.CreateIfNotExists(); + } + +#if NET9_0_OR_GREATER + _respawner = await Respawner.CreateAsync(_connectionString, new RespawnerOptions + { + DbAdapter = DbAdapter.SqlServer, + SchemasToInclude = new[] { "dbo" }, + }); + await ResetDatabase(); +#elif NET472 + _respawner = new Checkpoint + { + DbAdapter = DbAdapter.SqlServer, + SchemasToInclude = new[] { "dbo" }, + }; + await ResetDatabase(); +#endif + } + + public async Task DisposeAsync() + { + if (_dbContainer is not null) + { + await _dbContainer.StopAsync(); + } + else + { + //using var dbContext = new TestDbContext(_connectionString); + //dbContext.Database.Delete(); + } + } + + private static MsSqlContainer CreateContainer() => new MsSqlBuilder() + .WithImage("mcr.microsoft.com/mssql/server:2022-latest") + .WithPassword("P@ssW0rd!") + .Build(); +} diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/GlobalUsings.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/GlobalUsings.cs index b0608534..c1450d15 100644 --- a/tests/Ardalis.Specification.EntityFramework6.Tests/GlobalUsings.cs +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/GlobalUsings.cs @@ -5,5 +5,4 @@ global using System.Collections.Generic; global using System.Linq; global using System.Threading.Tasks; -global using Tests.Fixture; global using Xunit; diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_AnyAsync.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_AnyAsync.cs index 12840eeb..1214976e 100644 --- a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_AnyAsync.cs +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_AnyAsync.cs @@ -1,4 +1,6 @@ -namespace Tests; +using Tests.Fixture; + +namespace Tests; [Collection("ReadCollection")] public class RepositoryOfT_AnyAsync diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_DeleteRangeAsync.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_DeleteRangeAsync.cs index 4b1f1359..dfc262ca 100644 --- a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_DeleteRangeAsync.cs +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_DeleteRangeAsync.cs @@ -1,4 +1,6 @@ -namespace Tests; +using Tests.Fixture; + +namespace Tests; [Collection("WriteCollection")] public class RepositoryOfT_DeleteRangeAsync diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_GetById.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_GetById.cs index 14a8a9ef..a1a87acb 100644 --- a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_GetById.cs +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_GetById.cs @@ -1,4 +1,6 @@ -namespace Tests; +using Tests.Fixture; + +namespace Tests; [Collection("ReadCollection")] public class RepositoryOfT_GetById diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_GetBySpec.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_GetBySpec.cs index 8b2f855f..66656bb7 100644 --- a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_GetBySpec.cs +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_GetBySpec.cs @@ -1,4 +1,5 @@ -using System.Data.Entity; +using Tests.Fixture; +using System.Data.Entity; namespace Tests; diff --git a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_ListAsync.cs b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_ListAsync.cs index 8b255ac8..6a8a797f 100644 --- a/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_ListAsync.cs +++ b/tests/Ardalis.Specification.EntityFramework6.Tests/RepositoryOfT_ListAsync.cs @@ -1,4 +1,6 @@ -namespace Tests; +using Tests.Fixture; + +namespace Tests; [Collection("ReadCollection")] public class RepositoryOfT_ListAsync diff --git a/tests/Ardalis.Specification.EntityFrameworkCore.Tests/Fixture/TestFactory.cs b/tests/Ardalis.Specification.EntityFrameworkCore.Tests/Fixture/TestFactory.cs index 5e8b2abb..e8a25279 100644 --- a/tests/Ardalis.Specification.EntityFrameworkCore.Tests/Fixture/TestFactory.cs +++ b/tests/Ardalis.Specification.EntityFrameworkCore.Tests/Fixture/TestFactory.cs @@ -29,7 +29,7 @@ public async Task InitializeAsync() } else { - _connectionString = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SpecificationEFCoreTestsDB;Integrated Security=SSPI;TrustServerCertificate=True;"; + _connectionString = "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SpecificationTestsDB_EFCore;Integrated Security=SSPI;TrustServerCertificate=True;"; } } @@ -70,7 +70,7 @@ public async Task DisposeAsync() private static MsSqlContainer CreateContainer() => new MsSqlBuilder() .WithImage("mcr.microsoft.com/mssql/server:2022-latest") - .WithName("SpecificationEFCoreTestsDB") + .WithName("SpecificationTestsDB_EFCore") .WithPassword("P@ssW0rd!") .Build(); }