Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using Faura.Infrastructure.IntegrationTesting.Constants;
using Faura.Infrastructure.IntegrationTesting.Seeders;
namespace Faura.Infrastructure.IntegrationTesting.Factory;

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Faura.Infrastructure.IntegrationTesting.Constants;
using Faura.Infrastructure.IntegrationTesting.Seeders;
using Xunit;

namespace Faura.Infrastructure.IntegrationTesting.Factory;

/// <summary>
/// Base factory for integration tests with container setup and service customization.
/// </summary>
Expand All @@ -16,47 +16,67 @@ public abstract class BaseWebApplicationFactory<TEntryPoint>
IAsyncLifetime
where TEntryPoint : class
{
/// <summary>
/// Final configuration used during the test run.
/// </summary>
protected IConfiguration? Configuration;
private static readonly SemaphoreSlim _initializationLock = new(1, 1);
private static bool _isInitialized;
private static IConfiguration? _sharedConfiguration;

protected IConfiguration? Configuration { get; set; }

public async Task InitializeAsync()
{
Environment.SetEnvironmentVariable(
"ASPNETCORE_ENVIRONMENT",
IntegrationTestConstants.Environment
);
await _initializationLock.WaitAsync();
try
{
if (!_isInitialized)
{
Environment.SetEnvironmentVariable(
"ASPNETCORE_ENVIRONMENT",
IntegrationTestConstants.Environment);

var testProjectPath = Directory.GetCurrentDirectory();
var settingsFile = Path.Combine(
testProjectPath,
$"appsettings.{IntegrationTestConstants.Environment}.json"
);
var testProjectPath = Directory.GetCurrentDirectory();
var settingsFile = Path.Combine(
testProjectPath,
$"appsettings.{IntegrationTestConstants.Environment}.json");

var configBuilder = new ConfigurationBuilder()
.AddJsonFile(settingsFile, optional: false)
.AddEnvironmentVariables();
var configBuilder = new ConfigurationBuilder()
.AddJsonFile(settingsFile, optional: false)
.AddEnvironmentVariables();

await ConfigureConfigurationAsync(configBuilder);
await ConfigureConfigurationAsync(configBuilder);

var baseConfig = configBuilder.Build();
_sharedConfiguration = await ConfigureTestContainersAsync(baseConfig);

_isInitialized = true;
}

var baseConfig = configBuilder.Build();
Configuration = await ConfigureTestContainersAsync(baseConfig);
Configuration = _sharedConfiguration;
}
finally
{
_initializationLock.Release();
}
}

public virtual Task DisposeAsync() => Task.CompletedTask;
public override async ValueTask DisposeAsync()
{
await base.DisposeAsync();
GC.SuppressFinalize(this);
}

async Task IAsyncLifetime.DisposeAsync() => await DisposeAsync();

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment(IntegrationTestConstants.Environment);

builder.ConfigureAppConfiguration(
(_, config) =>
{
config.Sources.Clear();
config.AddConfiguration(Configuration!);
}
);
builder
.UseConfiguration(Configuration!)
.ConfigureAppConfiguration(
(_, config) =>
{
config.AddConfiguration(Configuration!);
});

builder.ConfigureServices(services =>
{
Expand All @@ -65,7 +85,10 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)

using var provider = services.BuildServiceProvider(validateScopes: true);
using var scope = provider.CreateScope();
RunSeedersAsync(scope.ServiceProvider).GetAwaiter().GetResult();

BaseWebApplicationFactory<TEntryPoint>.RunSeedersAsync(scope.ServiceProvider)
.GetAwaiter()
.GetResult();
});
}

Expand All @@ -79,26 +102,27 @@ protected virtual Task ConfigureConfigurationAsync(IConfigurationBuilder builder
/// Starts containers and returns updated configuration (e.g., with connection strings).
/// </summary>
protected virtual Task<IConfiguration> ConfigureTestContainersAsync(
IConfiguration configuration
) => Task.FromResult(configuration);
IConfiguration configuration) => Task.FromResult(configuration);

/// <summary>
/// Registers test-specific or mocked services.
/// </summary>
protected virtual void ConfigureTestServices(
IServiceCollection services,
IConfiguration configuration
) { }
IConfiguration configuration)
{
}

/// <summary>
/// Registers database context and ensures schema is created.
/// </summary>
protected virtual void ConfigureTestDatabase(
IServiceCollection services,
IConfiguration configuration
) { }
IConfiguration configuration)
{
}

private async Task RunSeedersAsync(IServiceProvider serviceProvider)
private static async Task RunSeedersAsync(IServiceProvider serviceProvider)
{
foreach (var seeder in serviceProvider.GetServices<ITestDataSeeder>())
{
Expand All @@ -113,4 +137,4 @@ private async Task RunSeedersAsync(IServiceProvider serviceProvider)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,45 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Description>Faura Integration Testing</Description>
<license>https://github.com/JosepFe/faura/blob/master/LICENSE</license>
<Description>Faura Integration Testing Library</Description>
<RepositoryType>git</RepositoryType>
<PackageTags>faura;testing;integrationTesting</PackageTags>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageReleaseNotes>NET 8.0+ compatibility version</PackageReleaseNotes>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<NoWarn>$(NoWarn);NETSDK1206;1701;1702;NU1608;CA1822</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.16" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.14" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.17" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.17" />
<PackageReference Include="Testcontainers" Version="4.6.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="Testcontainers" Version="4.4.0" />
<PackageReference Include="Testcontainers.MongoDb" Version="4.4.0" />
<PackageReference Include="Testcontainers.MsSql" Version="4.4.0" />
<PackageReference Include="Testcontainers.PostgreSql" Version="4.4.0" />
<PackageReference Include="Testcontainers.Redis" Version="4.4.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.core" Version="2.9.3" />
</ItemGroup>

<ItemGroup>
<None Include="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<SonarQubeSetting Include="sonar.coverage.exclusions">
<Value>**/**/*.cs</Value>
</SonarQubeSetting>
</ItemGroup>

</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public class ContainerOptions
{
public string Image { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public int Port { get; set; }
public int? Port { get; set; }
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string Database { get; set; } = string.Empty;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
namespace Faura.Infrastructure.IntegrationTesting.Options;
namespace Faura.Infrastructure.IntegrationTesting.Options;

public class TestContainerOptions
{
public ContainerOptions Mongo { get; set; } = new();
public ContainerOptions Redis { get; set; } = new();
public ContainerOptions SqlServer { get; set; } = new ();
public ContainerOptions Postgres { get; set; } = new();
public ContainerOptions SqlServer { get; set; } = new();
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Faura.Infrastructure.IntegrationTesting.Seeders;

namespace Faura.Infrastructure.IntegrationTesting.Seeders;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

public abstract class TestDataSeeder<TContext> : ITestDataSeeder
where TContext : DbContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Faura.Infrastructure.IntegrationTesting.Options;
using Faura.Infrastructure.IntegrationTesting.TestContainers.Constants;
using Faura.Infrastructure.IntegrationTesting.TestContainers.Core;

namespace Faura.Infrastructure.IntegrationTesting.TestContainers.Configurations;

public class MongoContainerConfiguration : ITestContainerConfiguration
{
private const string DefaultImage = "mongo:7-jammy";
private const int MongoInternalPort = 27017;

private readonly ContainerOptions _options;

public MongoContainerConfiguration(ContainerOptions options)
Expand All @@ -15,17 +17,44 @@ public MongoContainerConfiguration(ContainerOptions options)

public string Image =>
string.IsNullOrWhiteSpace(_options.Image)
? ContainerDefaultsConstants.Images.Mongo
? DefaultImage
: _options.Image;

public int Port => _options.Port != 0 ? _options.Port : ContainerDefaultsConstants.Ports.Mongo;
public int? Port => _options.Port;

public int InternalPort => MongoInternalPort;

public string Username => _options.Username ?? string.Empty;

public string Password => _options.Password ?? string.Empty;

public string Database => _options.Database ?? "test";

public Dictionary<string, string> GetEnvironmentVariables() => new();
public Dictionary<string, string> GetEnvironmentVariables()
{
var env = new Dictionary<string, string>();

if (!string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(Password))
{
env["MONGO_INITDB_ROOT_USERNAME"] = Username;
env["MONGO_INITDB_ROOT_PASSWORD"] = Password;
}

public string BuildConnectionString(int mappedPort) =>
$"mongodb://localhost:{mappedPort}/{Database}";
}
if (!string.IsNullOrWhiteSpace(Database))
{
env["MONGO_INITDB_DATABASE"] = Database;
}

return env;
}

public string BuildConnectionString(string host, int mappedPort)
{
if (!string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(Password))
{
return $"mongodb://{Username}:{Password}@{host}:{mappedPort}/{Database}?authSource=admin";
}

return $"mongodb://{host}:{mappedPort}/{Database}";
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Faura.Infrastructure.IntegrationTesting.Options;
using Faura.Infrastructure.IntegrationTesting.TestContainers.Constants;
using Faura.Infrastructure.IntegrationTesting.TestContainers.Core;
namespace Faura.Infrastructure.IntegrationTesting.TestContainers.Configurations;

namespace Faura.Infrastructure.IntegrationTesting.TestContainers.Configurations;
using Faura.Infrastructure.IntegrationTesting.Options;
using Faura.Infrastructure.IntegrationTesting.TestContainers.Core;

public class PostgresContainerConfiguration : ITestContainerConfiguration
{
private const string DefaultImage = "postgres:15-alpine";
private const int PostgresInternalPort = 5432;

private readonly ContainerOptions _options;

public PostgresContainerConfiguration(ContainerOptions options)
Expand All @@ -15,11 +17,12 @@ public PostgresContainerConfiguration(ContainerOptions options)

public string Image =>
string.IsNullOrWhiteSpace(_options.Image)
? ContainerDefaultsConstants.Images.Postgres
? DefaultImage
: _options.Image;

public int Port =>
_options.Port != 0 ? _options.Port : ContainerDefaultsConstants.Ports.Postgres;
public int? Port => _options.Port;

public int InternalPort => PostgresInternalPort;

public string Username => _options.Username ?? "postgres";
public string Password => _options.Password ?? "postgres";
Expand All @@ -33,6 +36,6 @@ public Dictionary<string, string> GetEnvironmentVariables() =>
["POSTGRES_DB"] = Database,
};

public string BuildConnectionString(int mappedPort) =>
$"Host=localhost;Port={mappedPort};Username={Username};Password={Password};Database={Database}";
public string BuildConnectionString(string host, int mappedPort) =>
$"Host={host};Port={mappedPort};Username={Username};Password={Password};Database={Database}";
}
Loading
Loading