diff --git a/CleanArchitecture.Api/appsettings.Integration.json b/CleanArchitecture.Api/appsettings.Integration.json
index 973f184..446fd0c 100644
--- a/CleanArchitecture.Api/appsettings.Integration.json
+++ b/CleanArchitecture.Api/appsettings.Integration.json
@@ -10,6 +10,6 @@
"Host": "localhost",
"Username": "guest",
"Password": "guest",
- "Enabled": "False"
+ "Enabled": "True"
}
}
diff --git a/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj b/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj
index dba5f41..b2e8bfd 100644
--- a/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj
+++ b/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj
@@ -5,6 +5,7 @@
enable
false
+ true
diff --git a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj
index aa672b4..e788e28 100644
--- a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj
+++ b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj
@@ -5,6 +5,7 @@
enable
false
+ true
diff --git a/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj b/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj
index 6f4594c..3267fbe 100644
--- a/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj
+++ b/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj
@@ -5,6 +5,7 @@
enable
false
+ true
diff --git a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj
index 6531f2a..4a23d64 100644
--- a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj
+++ b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj
@@ -5,6 +5,7 @@
enable
false
+ true
@@ -12,17 +13,17 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
runtime; build; native; contentfiles; analyzers; buildtransitive
all
@@ -34,5 +35,7 @@
-
+
+
+
diff --git a/CleanArchitecture.IntegrationTests/Constants/Configuration.cs b/CleanArchitecture.IntegrationTests/Constants/Configuration.cs
new file mode 100644
index 0000000..a7774c6
--- /dev/null
+++ b/CleanArchitecture.IntegrationTests/Constants/Configuration.cs
@@ -0,0 +1,8 @@
+namespace CleanArchitecture.IntegrationTests.Constants;
+
+public static class Configuration
+{
+ public const int RedisPort = 6379;
+ public const int MsSqlPort = 1433;
+ public const int RabbitMqPort = 5673;
+}
diff --git a/CleanArchitecture.IntegrationTests/Controller/TenantControllerTests.cs b/CleanArchitecture.IntegrationTests/Controller/TenantControllerTests.cs
index 5b5abfd..d406a4d 100644
--- a/CleanArchitecture.IntegrationTests/Controller/TenantControllerTests.cs
+++ b/CleanArchitecture.IntegrationTests/Controller/TenantControllerTests.cs
@@ -7,24 +7,17 @@ using CleanArchitecture.Application.ViewModels.Tenants;
using CleanArchitecture.IntegrationTests.Extensions;
using CleanArchitecture.IntegrationTests.Fixtures;
using FluentAssertions;
-using Xunit;
-using Xunit.Priority;
namespace CleanArchitecture.IntegrationTests.Controller;
-[Collection("IntegrationTests")]
-[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
-public sealed class TenantControllerTests : IClassFixture
+public sealed class TenantControllerTests
{
- private readonly TenantTestFixture _fixture;
+ private readonly TenantTestFixture _fixture = new();
- public TenantControllerTests(TenantTestFixture fixture)
- {
- _fixture = fixture;
- }
+ [OneTimeSetUp]
+ public async Task Setup() => await _fixture.SeedTestData();
- [Fact]
- [Priority(0)]
+ [Test, Order(0)]
public async Task Should_Get_Tenant_By_Id()
{
var response = await _fixture.ServerClient.GetAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}");
@@ -41,8 +34,7 @@ public sealed class TenantControllerTests : IClassFixture
message.Data.Users.Count().Should().Be(1);
}
- [Fact]
- [Priority(5)]
+ [Test, Order(1)]
public async Task Should_Get_All_Tenants()
{
var response = await _fixture.ServerClient.GetAsync(
@@ -63,8 +55,7 @@ public sealed class TenantControllerTests : IClassFixture
.Users.Count().Should().Be(1);
}
- [Fact]
- [Priority(10)]
+ [Test, Order(2)]
public async Task Should_Create_Tenant()
{
var request = new CreateTenantViewModel("Test Tenant 2");
@@ -89,8 +80,7 @@ public sealed class TenantControllerTests : IClassFixture
tenantMessage.Data.Name.Should().Be(request.Name);
}
- [Fact]
- [Priority(15)]
+ [Test, Order(3)]
public async Task Should_Update_Tenant()
{
var request = new UpdateTenantViewModel(_fixture.CreatedTenantId, "Test Tenant 3");
@@ -117,8 +107,7 @@ public sealed class TenantControllerTests : IClassFixture
tenantMessage.Data.Name.Should().Be(request.Name);
}
- [Fact]
- [Priority(20)]
+ [Test, Order(4)]
public async Task Should_Delete_Tenant()
{
var response = await _fixture.ServerClient.DeleteAsync($"/api/v1/Tenant/{_fixture.CreatedTenantId}");
diff --git a/CleanArchitecture.IntegrationTests/Controller/UserControllerTests.cs b/CleanArchitecture.IntegrationTests/Controller/UserControllerTests.cs
index 5d4b3d7..ab858ed 100644
--- a/CleanArchitecture.IntegrationTests/Controller/UserControllerTests.cs
+++ b/CleanArchitecture.IntegrationTests/Controller/UserControllerTests.cs
@@ -10,24 +10,17 @@ using CleanArchitecture.IntegrationTests.Extensions;
using CleanArchitecture.IntegrationTests.Fixtures;
using CleanArchitecture.IntegrationTests.Infrastructure.Auth;
using FluentAssertions;
-using Xunit;
-using Xunit.Priority;
namespace CleanArchitecture.IntegrationTests.Controller;
-[Collection("IntegrationTests")]
-[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
-public sealed class UserControllerTests : IClassFixture
+public sealed class UserControllerTests
{
- private readonly UserTestFixture _fixture;
+ private readonly UserTestFixture _fixture = new();
- public UserControllerTests(UserTestFixture fixture)
- {
- _fixture = fixture;
- }
+ [OneTimeSetUp]
+ public async Task Setup() => await GlobalSetupFixture.RespawnDatabaseAsync();
- [Fact]
- [Priority(0)]
+ [Test, Order(0)]
public async Task Should_Get_All_User()
{
var response = await _fixture.ServerClient.GetAsync("/api/v1/user");
@@ -50,8 +43,7 @@ public sealed class UserControllerTests : IClassFixture
currentUser.LastName.Should().Be(TestAuthenticationOptions.LastName);
}
- [Fact]
- [Priority(5)]
+ [Test, Order(1)]
public async Task Should_Get_User_By_Id()
{
var response = await _fixture.ServerClient.GetAsync("/api/v1/user/" + TestAuthenticationOptions.TestUserId);
@@ -70,8 +62,7 @@ public sealed class UserControllerTests : IClassFixture
content.LastName.Should().Be(TestAuthenticationOptions.LastName);
}
- [Fact]
- [Priority(10)]
+ [Test, Order(2)]
public async Task Should_Create_User()
{
var user = new CreateUserViewModel(
@@ -89,8 +80,7 @@ public sealed class UserControllerTests : IClassFixture
message?.Data.Should().NotBeEmpty();
}
- [Fact]
- [Priority(15)]
+ [Test, Order(3)]
public async Task Should_Login_User()
{
var user = new LoginUserViewModel(
@@ -105,8 +95,7 @@ public sealed class UserControllerTests : IClassFixture
message?.Data.Should().NotBeEmpty();
}
- [Fact]
- [Priority(20)]
+ [Test, Order(4)]
public async Task Should_Get_The_Current_Active_Users()
{
var response = await _fixture.ServerClient.GetAsync("/api/v1/user/me");
@@ -125,8 +114,7 @@ public sealed class UserControllerTests : IClassFixture
content.LastName.Should().Be(TestAuthenticationOptions.LastName);
}
- [Fact]
- [Priority(25)]
+ [Test, Order(5)]
public async Task Should_Update_User()
{
var user = new UpdateUserViewModel(
@@ -167,8 +155,7 @@ public sealed class UserControllerTests : IClassFixture
userContent.Role.Should().Be(user.Role);
}
- [Fact]
- [Priority(30)]
+ [Test, Order(6)]
public async Task Should_Change_User_Password()
{
var user = new ChangePasswordViewModel(
@@ -201,8 +188,7 @@ public sealed class UserControllerTests : IClassFixture
loginMessage?.Data.Should().NotBeEmpty();
}
- [Fact]
- [Priority(35)]
+ [Test, Order(7)]
public async Task Should_Delete_User()
{
var response = await _fixture.ServerClient.DeleteAsync("/api/v1/user/" + TestAuthenticationOptions.TestUserId);
diff --git a/CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs
deleted file mode 100644
index a98b43d..0000000
--- a/CleanArchitecture.IntegrationTests/Fixtures/AccessorFixture.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using CleanArchitecture.IntegrationTests.Infrastructure;
-using Xunit;
-
-namespace CleanArchitecture.IntegrationTests.Fixtures;
-
-public sealed class AccessorFixture : IAsyncLifetime
-{
- public static string TestRunDbName { get; } = $"CleanArchitecture-Integration-{Guid.NewGuid()}";
-
- public async Task DisposeAsync()
- {
- var db = DatabaseAccessor.GetOrCreateAsync(TestRunDbName);
- await db.DisposeAsync();
-
- var redis = RedisAccessor.GetOrCreateAsync();
- await redis.DisposeAsync();
-
- var rabbit = RabbitmqAccessor.GetOrCreateAsync();
- await rabbit.DisposeAsync();
- }
-
- public async Task InitializeAsync()
- {
- var db = DatabaseAccessor.GetOrCreateAsync(TestRunDbName);
- await db.InitializeAsync();
-
- var redis = RedisAccessor.GetOrCreateAsync();
- await redis.InitializeAsync();
-
- var rabbit = RabbitmqAccessor.GetOrCreateAsync();
- await rabbit.InitializeAsync();
- }
-}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/Fixtures/TenantTestFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/TenantTestFixture.cs
index 0d9a821..7416837 100644
--- a/CleanArchitecture.IntegrationTests/Fixtures/TenantTestFixture.cs
+++ b/CleanArchitecture.IntegrationTests/Fixtures/TenantTestFixture.cs
@@ -1,7 +1,9 @@
using System;
+using System.Threading.Tasks;
using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Domain.Enums;
using CleanArchitecture.Infrastructure.Database;
+using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.IntegrationTests.Fixtures;
@@ -9,9 +11,11 @@ public sealed class TenantTestFixture : TestFixtureBase
{
public Guid CreatedTenantId { get; } = Guid.NewGuid();
- protected override void SeedTestData(ApplicationDbContext context)
+ public async Task SeedTestData()
{
- base.SeedTestData(context);
+ await GlobalSetupFixture.RespawnDatabaseAsync();
+
+ using var context = Factory.Services.GetRequiredService();
context.Tenants.Add(new Tenant(
CreatedTenantId,
@@ -26,6 +30,6 @@ public sealed class TenantTestFixture : TestFixtureBase
"Test User",
UserRole.User));
- context.SaveChanges();
+ await context.SaveChangesAsync();
}
}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs b/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs
index 3ba93ae..f5b8290 100644
--- a/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs
+++ b/CleanArchitecture.IntegrationTests/Fixtures/TestFixtureBase.cs
@@ -1,18 +1,11 @@
using System;
using System.Net.Http;
-using System.Threading.Tasks;
-using CleanArchitecture.Domain.Constants;
-using CleanArchitecture.Domain.Entities;
-using CleanArchitecture.Domain.Enums;
-using CleanArchitecture.Infrastructure.Database;
using CleanArchitecture.IntegrationTests.Infrastructure;
-using CleanArchitecture.IntegrationTests.Infrastructure.Auth;
using Microsoft.Extensions.DependencyInjection;
-using Xunit;
namespace CleanArchitecture.IntegrationTests.Fixtures;
-public class TestFixtureBase : IAsyncLifetime
+public class TestFixtureBase
{
public HttpClient ServerClient { get; }
protected CleanArchitectureWebApplicationFactory Factory { get; }
@@ -21,64 +14,16 @@ public class TestFixtureBase : IAsyncLifetime
{
Factory = new CleanArchitectureWebApplicationFactory(
RegisterCustomServicesHandler,
- useTestAuthentication,
- AccessorFixture.TestRunDbName);
+ useTestAuthentication);
ServerClient = Factory.CreateClient();
ServerClient.Timeout = TimeSpan.FromMinutes(5);
}
- protected virtual void SeedTestData(ApplicationDbContext context)
- {
- }
-
- private async Task PrepareDatabaseAsync()
- {
- await Factory.RespawnDatabaseAsync();
-
- using var scope = Factory.Services.CreateScope();
- await using var dbContext = scope.ServiceProvider.GetRequiredService();
-
- dbContext.Tenants.Add(new Tenant(
- Ids.Seed.TenantId,
- "Admin Tenant"));
-
- dbContext.Users.Add(new User(
- Ids.Seed.UserId,
- Ids.Seed.TenantId,
- "admin@email.com",
- "Admin",
- "User",
- "$2a$12$Blal/uiFIJdYsCLTMUik/egLbfg3XhbnxBC6Sb5IKz2ZYhiU/MzL2",
- UserRole.Admin));
-
- dbContext.Users.Add(new User(
- TestAuthenticationOptions.TestUserId,
- Ids.Seed.TenantId,
- TestAuthenticationOptions.Email,
- TestAuthenticationOptions.FirstName,
- TestAuthenticationOptions.LastName,
- TestAuthenticationOptions.Password,
- UserRole.Admin));
-
- SeedTestData(dbContext);
- await dbContext.SaveChangesAsync();
- }
-
protected virtual void RegisterCustomServicesHandler(
IServiceCollection services,
ServiceProvider serviceProvider,
IServiceProvider scopedServices)
{
}
-
- public async Task InitializeAsync()
- {
- await PrepareDatabaseAsync();
- }
-
- public Task DisposeAsync()
- {
- return Task.CompletedTask;
- }
}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetTenantsByIdsTestFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetTenantsByIdsTestFixture.cs
index fdfe5a6..05c381a 100644
--- a/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetTenantsByIdsTestFixture.cs
+++ b/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetTenantsByIdsTestFixture.cs
@@ -1,7 +1,9 @@
using System;
+using System.Threading.Tasks;
using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Infrastructure.Database;
using Grpc.Net.Client;
+using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.IntegrationTests.Fixtures.gRPC;
@@ -18,14 +20,16 @@ public sealed class GetTenantsByIdsTestFixture : TestFixtureBase
});
}
- protected override void SeedTestData(ApplicationDbContext context)
+ public async Task SeedTestData()
{
- base.SeedTestData(context);
+ await GlobalSetupFixture.RespawnDatabaseAsync();
+
+ using var context = Factory.Services.GetRequiredService();
var tenant = CreateTenant();
context.Tenants.Add(tenant);
- context.SaveChanges();
+ await context.SaveChangesAsync();
}
public Tenant CreateTenant()
diff --git a/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetUsersByIdsTestFixture.cs b/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetUsersByIdsTestFixture.cs
index 22ce59b..09fa30e 100644
--- a/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetUsersByIdsTestFixture.cs
+++ b/CleanArchitecture.IntegrationTests/Fixtures/gRPC/GetUsersByIdsTestFixture.cs
@@ -1,9 +1,11 @@
using System;
+using System.Threading.Tasks;
using CleanArchitecture.Domain.Constants;
using CleanArchitecture.Domain.Entities;
using CleanArchitecture.Domain.Enums;
using CleanArchitecture.Infrastructure.Database;
using Grpc.Net.Client;
+using Microsoft.Extensions.DependencyInjection;
namespace CleanArchitecture.IntegrationTests.Fixtures.gRPC;
@@ -20,14 +22,16 @@ public sealed class GetUsersByIdsTestFixture : TestFixtureBase
});
}
- protected override void SeedTestData(ApplicationDbContext context)
+ public async Task SeedTestData()
{
- base.SeedTestData(context);
+ await GlobalSetupFixture.RespawnDatabaseAsync();
+
+ using var context = Factory.Services.GetRequiredService();
var user = CreateUser();
context.Users.Add(user);
- context.SaveChanges();
+ await context.SaveChangesAsync();
}
public User CreateUser()
diff --git a/CleanArchitecture.IntegrationTests/GlobalSetupFixture.cs b/CleanArchitecture.IntegrationTests/GlobalSetupFixture.cs
new file mode 100644
index 0000000..7805a7d
--- /dev/null
+++ b/CleanArchitecture.IntegrationTests/GlobalSetupFixture.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Threading.Tasks;
+using CleanArchitecture.IntegrationTests.Constants;
+using Respawn;
+using Testcontainers.MsSql;
+using Testcontainers.RabbitMq;
+using Testcontainers.Redis;
+
+namespace CleanArchitecture.IntegrationTests;
+
+[SetUpFixture]
+internal class GlobalSetupFixture
+{
+ private static Respawner? s_respawner;
+
+ public static MsSqlContainer DatabaseContainer { get; } = new MsSqlBuilder()
+ .WithPortBinding(Configuration.MsSqlPort, assignRandomHostPort: true)
+ .Build();
+
+ public static RedisContainer RedisContainer { get; } = new RedisBuilder()
+ .WithPortBinding(Configuration.RedisPort, assignRandomHostPort: true)
+ .Build();
+
+ public static RabbitMqContainer RabbitContainer { get; } = new RabbitMqBuilder()
+ .WithPortBinding(Configuration.RabbitMqPort, assignRandomHostPort: true)
+ .Build();
+
+ public static string DatabaseConnectionString { get; private set; } = string.Empty;
+
+ [OneTimeSetUp]
+ public async Task SetUp()
+ {
+ await DatabaseContainer.StartAsync();
+ await RedisContainer.StartAsync();
+ await RabbitContainer.StartAsync();
+
+ DatabaseConnectionString = DatabaseContainer
+ .GetConnectionString()
+ .Replace("Database=master", $"Database=clean-architecture-{Guid.NewGuid()}");
+ }
+
+ [OneTimeTearDown]
+ public async Task TearDown()
+ {
+ await DatabaseContainer.DisposeAsync();
+ await RedisContainer.DisposeAsync();
+ await RabbitContainer.DisposeAsync();
+ }
+
+ public static async Task RespawnDatabaseAsync()
+ {
+ if (s_respawner is null)
+ {
+ try
+ {
+ s_respawner = await Respawner.CreateAsync(
+ DatabaseConnectionString,
+ new RespawnerOptions
+ {
+ TablesToIgnore = ["__EFMigrationsHistory"]
+ });
+ }
+ catch (Exception ex)
+ {
+ // Creation of the respawner can fail if the database has not been created yet
+ TestContext.WriteLine($"Failed to create respawner: {ex.Message}");
+ }
+ }
+
+ if (s_respawner is not null)
+ {
+ await s_respawner.ResetAsync(DatabaseConnectionString);
+ }
+ }
+}
diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs b/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs
index 2fde656..8a17ef9 100644
--- a/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs
+++ b/CleanArchitecture.IntegrationTests/Infrastructure/CleanArchitectureWebApplicationFactory.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
-using CleanArchitecture.Infrastructure.Database;
+using CleanArchitecture.IntegrationTests.Constants;
using CleanArchitecture.IntegrationTests.Infrastructure.Auth;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
@@ -12,26 +11,20 @@ namespace CleanArchitecture.IntegrationTests.Infrastructure;
public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFactory
{
- public delegate void AddCustomSeedDataHandler(ApplicationDbContext context);
-
public delegate void RegisterCustomServicesHandler(
IServiceCollection services,
ServiceProvider serviceProvider,
IServiceProvider scopedServices);
- private readonly string _instanceDatabaseName;
-
private readonly bool _addTestAuthentication;
private readonly RegisterCustomServicesHandler? _registerCustomServicesHandler;
public CleanArchitectureWebApplicationFactory(
RegisterCustomServicesHandler? registerCustomServicesHandler,
- bool addTestAuthentication,
- string instanceDatabaseName)
+ bool addTestAuthentication)
{
_registerCustomServicesHandler = registerCustomServicesHandler;
_addTestAuthentication = addTestAuthentication;
- _instanceDatabaseName = instanceDatabaseName;
}
protected override void ConfigureWebHost(IWebHostBuilder builder)
@@ -40,31 +33,18 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto
base.ConfigureWebHost(builder);
- var configuration = new ConfigurationBuilder()
- .Build();
-
- builder.ConfigureAppConfiguration(configurationBuilder =>
+ builder.ConfigureAppConfiguration(configuration =>
{
- configurationBuilder.AddEnvironmentVariables();
+ var redisPort = GlobalSetupFixture.RedisContainer.GetMappedPublicPort(Configuration.RedisPort);
- var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName);
- var redisAccessor = RedisAccessor.GetOrCreateAsync();
- var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync();
-
- // Overwrite default connection strings
- configurationBuilder.AddInMemoryCollection([
+ configuration.AddInMemoryCollection([
new KeyValuePair(
"ConnectionStrings:DefaultConnection",
- dbAccessor.GetConnectionString()),
+ GlobalSetupFixture.DatabaseConnectionString),
new KeyValuePair(
- "RedisHostName",
- redisAccessor.GetConnectionString()),
- new KeyValuePair(
- "RabbitMQ:Host",
- rabbitAccessor.GetConnectionString())
+ "RedisStackExchange:RedisConfigString",
+ $"localhost:{redisPort},abortConnect=true")
]);
-
- configuration = configurationBuilder.Build();
});
builder.ConfigureServices(services =>
@@ -82,38 +62,7 @@ public sealed class CleanArchitectureWebApplicationFactory : WebApplicationFacto
using var scope = sp.CreateScope();
var scopedServices = scope.ServiceProvider;
-
- var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName);
- dbAccessor.CreateDatabase(scopedServices);
-
- var redisAccessor = RedisAccessor.GetOrCreateAsync();
- redisAccessor.RegisterRedis(services, configuration);
-
- var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync();
- rabbitAccessor.RegisterRabbitmq(services, configuration);
-
_registerCustomServicesHandler?.Invoke(services, sp, scopedServices);
});
}
-
- public async Task RespawnDatabaseAsync()
- {
- var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName);
- await dbAccessor.RespawnDatabaseAsync();
-
- var redisAccessor = RedisAccessor.GetOrCreateAsync();
- redisAccessor.ResetRedis();
- }
-
- public override async ValueTask DisposeAsync()
- {
- var dbAccessor = DatabaseAccessor.GetOrCreateAsync(_instanceDatabaseName);
- await dbAccessor.DisposeAsync();
-
- var redisAccessor = RedisAccessor.GetOrCreateAsync();
- await redisAccessor.DisposeAsync();
-
- var rabbitAccessor = RabbitmqAccessor.GetOrCreateAsync();
- await rabbitAccessor.DisposeAsync();
- }
}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs
deleted file mode 100644
index ab1d837..0000000
--- a/CleanArchitecture.IntegrationTests/Infrastructure/DatabaseAccessor.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Threading.Tasks;
-using CleanArchitecture.Infrastructure.Database;
-using CleanArchitecture.Infrastructure.Extensions;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.DependencyInjection;
-using Respawn;
-using Testcontainers.MsSql;
-
-namespace CleanArchitecture.IntegrationTests.Infrastructure;
-
-public sealed class DatabaseAccessor
-{
- private static readonly ConcurrentDictionary s_accessors = new();
-
- private readonly string _instanceDatabaseName;
- private bool _databaseCreated = false;
- private readonly object _databaseCreationLock = new();
-
- private const string _dbPassword = "234#AD224fD#ss";
- private static readonly MsSqlContainer s_dbContainer = new MsSqlBuilder()
- .WithPassword(_dbPassword)
- .WithPortBinding(1433)
- .Build();
-
- public DatabaseAccessor(string instanceName)
- {
- _instanceDatabaseName = instanceName;
- }
-
- public async Task InitializeAsync()
- {
- await s_dbContainer.StartAsync();
- }
-
- public ApplicationDbContext CreateDatabase(IServiceProvider scopedServices)
- {
- var applicationDbContext = scopedServices.GetRequiredService();
-
- lock (_databaseCreationLock)
- {
- if (_databaseCreated)
- {
- return applicationDbContext;
- }
-
- applicationDbContext.EnsureMigrationsApplied();
-
- var eventsContext = scopedServices.GetRequiredService();
- eventsContext.EnsureMigrationsApplied();
-
- var notificationsContext = scopedServices.GetRequiredService();
- notificationsContext.EnsureMigrationsApplied();
- }
-
- _databaseCreated = true;
-
- return applicationDbContext;
- }
-
- public async ValueTask DisposeAsync()
- {
- // Reset the database to its original state
- var dropScript = $@"
- USE MASTER;
-
- ALTER DATABASE [{_instanceDatabaseName}]
- SET multi_user WITH ROLLBACK IMMEDIATE;
-
- ALTER DATABASE [{_instanceDatabaseName}]
- SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
-
- DROP DATABASE [{_instanceDatabaseName}];";
-
- await using (var con = new SqlConnection(GetConnectionString()))
- {
- await con.OpenAsync();
-
- var cmd = new SqlCommand(dropScript, con);
- await cmd.ExecuteNonQueryAsync();
- }
-
- await s_dbContainer.DisposeAsync();
- }
-
- public async Task RespawnDatabaseAsync()
- {
- var connectionString = GetConnectionString();
-
- var respawn = await Respawner.CreateAsync(
- connectionString,
- new RespawnerOptions
- {
- TablesToIgnore = ["__EFMigrationsHistory"]
- });
-
- await respawn.ResetAsync(connectionString);
- }
-
- public string GetConnectionString()
- {
- var conBuilder = new SqlConnectionStringBuilder()
- {
- DataSource = s_dbContainer.Hostname,
- InitialCatalog = _instanceDatabaseName,
- IntegratedSecurity = false,
- Password = _dbPassword,
- UserID = "sa",
- TrustServerCertificate = true
- };
-
- return conBuilder.ToString();
- }
-
- public static DatabaseAccessor GetOrCreateAsync(string instanceName)
- {
- if (!s_accessors.TryGetValue(instanceName, out _))
- {
- var accessor = new DatabaseAccessor(instanceName);
- s_accessors.TryAdd(instanceName, accessor);
- }
-
- return s_accessors[instanceName];
- }
-}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs
deleted file mode 100644
index 0e5ea8d..0000000
--- a/CleanArchitecture.IntegrationTests/Infrastructure/RabbitmqAccessor.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System.Collections.Concurrent;
-using System.Linq;
-using System.Threading.Tasks;
-using CleanArchitecture.Domain.Rabbitmq;
-using CleanArchitecture.Domain.Rabbitmq.Extensions;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Testcontainers.RabbitMq;
-using RabbitMqConfiguration = CleanArchitecture.Domain.Rabbitmq.RabbitMqConfiguration;
-
-namespace CleanArchitecture.IntegrationTests.Infrastructure;
-
-public sealed class RabbitmqAccessor
-{
- private static readonly ConcurrentDictionary s_accessors = new();
-
- private static readonly RabbitMqContainer s_rabbitContainer = new RabbitMqBuilder()
- .WithPortBinding(5672)
- .Build();
-
- public async Task InitializeAsync()
- {
- await s_rabbitContainer.StartAsync();
- }
-
- public async ValueTask DisposeAsync()
- {
- await s_rabbitContainer.DisposeAsync();
- }
-
- public string GetConnectionString()
- {
- return s_rabbitContainer.GetConnectionString();
- }
-
- public void RegisterRabbitmq(IServiceCollection serviceCollection, IConfiguration configuration)
- {
- var rabbitService = serviceCollection.FirstOrDefault(x =>
- x.ServiceType == typeof(RabbitMqHandler));
-
- if (rabbitService != null)
- {
- serviceCollection.Remove(rabbitService);
- }
-
- var rabbitConfig = serviceCollection.FirstOrDefault(x =>
- x.ServiceType == typeof(RabbitMqConfiguration));
-
- if (rabbitConfig != null)
- {
- serviceCollection.Remove(rabbitConfig);
- }
-
- serviceCollection.AddRabbitMqHandler(
- configuration,
- "RabbitMQ");
- }
-
- public static RabbitmqAccessor GetOrCreateAsync()
- {
- return s_accessors.GetOrAdd("rabbimq", _ => new RabbitmqAccessor());
- }
-}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs b/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs
deleted file mode 100644
index f461ab8..0000000
--- a/CleanArchitecture.IntegrationTests/Infrastructure/RedisAccessor.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System.Collections.Concurrent;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Caching.Distributed;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using StackExchange.Redis;
-using Testcontainers.Redis;
-
-namespace CleanArchitecture.IntegrationTests.Infrastructure;
-
-public sealed class RedisAccessor
-{
- private static readonly ConcurrentDictionary s_accessors = new();
-
- private static readonly RedisContainer s_redisContainer = new RedisBuilder()
- .WithPortBinding(6379)
- .Build();
-
- public async Task InitializeAsync()
- {
- await s_redisContainer.StartAsync();
- }
-
- public async ValueTask DisposeAsync()
- {
- await s_redisContainer.DisposeAsync();
- }
-
- public string GetConnectionString()
- {
- return s_redisContainer.GetConnectionString();
- }
-
- public void RegisterRedis(IServiceCollection serviceCollection, IConfiguration configuration)
- {
- var distributedCache = serviceCollection.FirstOrDefault(x =>
- x.ServiceType == typeof(IDistributedCache));
-
- if (distributedCache != null)
- {
- serviceCollection.Remove(distributedCache);
- }
-
- serviceCollection.AddStackExchangeRedisCache(options =>
- {
- options.Configuration = configuration["RedisHostName"];
- options.InstanceName = "clean-architecture";
- });
- }
-
- public void ResetRedis()
- {
- var redis = ConnectionMultiplexer.Connect(GetConnectionString());
-
- var db = redis.GetDatabase();
-
- var endpoints = redis.GetEndPoints();
- foreach (var endpoint in endpoints)
- {
- var server = redis.GetServer(endpoint);
- var keys = server.Keys();
- foreach (var key in keys)
- {
- db.KeyDelete(key);
- }
- }
-
- redis.Close();
- }
-
- public static RedisAccessor GetOrCreateAsync()
- {
- return s_accessors.GetOrAdd("redis", _ => new RedisAccessor());
- }
-}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs b/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs
deleted file mode 100644
index f44a4e5..0000000
--- a/CleanArchitecture.IntegrationTests/IntegrationTestsCollection.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using CleanArchitecture.IntegrationTests.Fixtures;
-using Xunit;
-
-namespace CleanArchitecture.IntegrationTests;
-
-[CollectionDefinition("IntegrationTests", DisableParallelization = true)]
-public sealed class IntegrationTestsCollection :
- ICollectionFixture
-{
-}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/UtilityTests/AuthTests.cs b/CleanArchitecture.IntegrationTests/UtilityTests/AuthTests.cs
index 7f3f26b..eea7c8f 100644
--- a/CleanArchitecture.IntegrationTests/UtilityTests/AuthTests.cs
+++ b/CleanArchitecture.IntegrationTests/UtilityTests/AuthTests.cs
@@ -2,33 +2,30 @@ using System.Net;
using System.Threading.Tasks;
using CleanArchitecture.IntegrationTests.Fixtures;
using FluentAssertions;
-using Xunit;
-using Xunit.Priority;
namespace CleanArchitecture.IntegrationTests.UtilityTests;
-[Collection("IntegrationTests")]
-[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
-public sealed class AuthTests : IClassFixture
+public sealed class AuthTests
{
- private readonly AuthTestFixure _fixture;
+ private readonly AuthTestFixure _fixture = new();
- public AuthTests(AuthTestFixure fixture)
- {
- _fixture = fixture;
- }
+ [OneTimeSetUp]
+ public async Task Setup() => await GlobalSetupFixture.RespawnDatabaseAsync();
+
+ [Datapoints]
+ public string[] values =
+ [
+ "/api/v1/user",
+ "/api/v1/user/me",
+ "/api/v1/user/d74b112a-ece0-443d-9b4f-85bc418822ca",
+ "/api/v1/tenant",
+ "/api/v1/tenant/d74b112a-ece0-443d-9b4f-85bc418822ca"
+ ];
[Theory]
- [InlineData("/api/v1/user")]
- [InlineData("/api/v1/user/me")]
- [InlineData("/api/v1/user/d74b112a-ece0-443d-9b4f-85bc418822ca")]
- [InlineData("/api/v1/tenant")]
- [InlineData("/api/v1/tenant/d74b112a-ece0-443d-9b4f-85bc418822ca")]
- public async Task Should_Get_Unauthorized_If_Trying_To_Call_Endpoint_Without_Token(
- string url)
+ public async Task Should_Get_Unauthorized_If_Trying_To_Call_Endpoint_Without_Token(string url)
{
var response = await _fixture.ServerClient.GetAsync(url);
-
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
}
\ No newline at end of file
diff --git a/CleanArchitecture.IntegrationTests/UtilityTests/HealthChecksTests.cs b/CleanArchitecture.IntegrationTests/UtilityTests/HealthChecksTests.cs
index 779f085..ec66a52 100644
--- a/CleanArchitecture.IntegrationTests/UtilityTests/HealthChecksTests.cs
+++ b/CleanArchitecture.IntegrationTests/UtilityTests/HealthChecksTests.cs
@@ -4,24 +4,17 @@ using CleanArchitecture.IntegrationTests.Fixtures;
using FluentAssertions;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Newtonsoft.Json.Linq;
-using Xunit;
-using Xunit.Priority;
namespace CleanArchitecture.IntegrationTests.UtilityTests;
-[Collection("IntegrationTests")]
-[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
-public sealed class HealthChecksTests : IClassFixture
+public sealed class HealthChecksTests
{
- private readonly AuthTestFixure _fixture;
+ private readonly AuthTestFixure _fixture = new();
- public HealthChecksTests(AuthTestFixure fixture)
- {
- _fixture = fixture;
- }
+ [OneTimeSetUp]
+ public async Task Setup() => await GlobalSetupFixture.RespawnDatabaseAsync();
- [Fact]
- [Priority(0)]
+ [Test, Order(0)]
public async Task Should_Return_Healthy()
{
var response = await _fixture.ServerClient.GetAsync("/healthz");
diff --git a/CleanArchitecture.IntegrationTests/gRPC/GetTenantsByIdsTests.cs b/CleanArchitecture.IntegrationTests/gRPC/GetTenantsByIdsTests.cs
index bef8dbf..a101f4e 100644
--- a/CleanArchitecture.IntegrationTests/gRPC/GetTenantsByIdsTests.cs
+++ b/CleanArchitecture.IntegrationTests/gRPC/GetTenantsByIdsTests.cs
@@ -4,23 +4,17 @@ using System.Threading.Tasks;
using CleanArchitecture.IntegrationTests.Fixtures.gRPC;
using CleanArchitecture.Proto.Tenants;
using FluentAssertions;
-using Xunit;
-using Xunit.Priority;
namespace CleanArchitecture.IntegrationTests.gRPC;
-[Collection("IntegrationTests")]
-[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
-public sealed class GetTenantsByIdsTests : IClassFixture
+public sealed class GetTenantsByIdsTests
{
- private readonly GetTenantsByIdsTestFixture _fixture;
+ private readonly GetTenantsByIdsTestFixture _fixture = new();
- public GetTenantsByIdsTests(GetTenantsByIdsTestFixture fixture)
- {
- _fixture = fixture;
- }
+ [OneTimeSetUp]
+ public async Task Setup() => await _fixture.SeedTestData();
- [Fact]
+ [Test]
public async Task Should_Get_Tenants_By_Ids()
{
var client = new TenantsApi.TenantsApiClient(_fixture.GrpcChannel);
diff --git a/CleanArchitecture.IntegrationTests/gRPC/GetUsersByIdsTests.cs b/CleanArchitecture.IntegrationTests/gRPC/GetUsersByIdsTests.cs
index 2353300..ab4a95c 100644
--- a/CleanArchitecture.IntegrationTests/gRPC/GetUsersByIdsTests.cs
+++ b/CleanArchitecture.IntegrationTests/gRPC/GetUsersByIdsTests.cs
@@ -3,23 +3,17 @@ using System.Threading.Tasks;
using CleanArchitecture.IntegrationTests.Fixtures.gRPC;
using CleanArchitecture.Proto.Users;
using FluentAssertions;
-using Xunit;
-using Xunit.Priority;
namespace CleanArchitecture.IntegrationTests.gRPC;
-[Collection("IntegrationTests")]
-[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
-public sealed class GetUsersByIdsTests : IClassFixture
+public sealed class GetUsersByIdsTests
{
- private readonly GetUsersByIdsTestFixture _fixture;
+ private readonly GetUsersByIdsTestFixture _fixture = new();
- public GetUsersByIdsTests(GetUsersByIdsTestFixture fixture)
- {
- _fixture = fixture;
- }
+ [OneTimeSetUp]
+ public async Task Setup() => await _fixture.SeedTestData();
- [Fact]
+ [Test]
public async Task Should_Get_Users_By_Ids()
{
var client = new UsersApi.UsersApiClient(_fixture.GrpcChannel);