Skip to content

Commit

Permalink
Add Testcontainers support and enhance output table testing
Browse files Browse the repository at this point in the history
Added a new project for supporting Testcontainers which includes interface for running Testcontainers and a builder for configuring them. Enhanced the TestOutputExtensions project to support console tables in test outputs. Additionally, refactored tests for better structure and organization.
  • Loading branch information
frankhaugen committed Jan 28, 2024
1 parent b0354e1 commit c43c04e
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
<PropertyGroup>
<RootNamespace>Xunit.Abstractions</RootNamespace>
<Description>Extends ITestOutputHelper to allow output of a generic type using a serializer.</Description>
<PackageTags>test, testing, extensions, xunit, output, helper, serializer, json, xml, csharp, dump, var, dumpvar, vardump, xmlserializer, jsonserializer, testoutputhelper, testoutput, outputhelper, ITestOutputHelper, abstractions, xunit.abstractions, xunit.abstractions.extensions, xunit.extensions.ordering</PackageTags>
<PackageTags>test, testing, extensions, xunit, output, helper, table, output, serializer, json, xml, csharp, dump, var, dumpvar, vardump, xmlserializer, jsonserializer, testoutputhelper, testoutput, outputhelper, ITestOutputHelper, abstractions, xunit.abstractions, xunit.abstractions.extensions, xunit.extensions.ordering</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Frank.Reflection.Dump" Version="1.3.0" />
<PackageReference Include="xunit.abstractions" Version="2.0.3" />
<PackageReference Include="ConsoleTableExt" Version="3.2.0"/>
<PackageReference Include="Frank.Reflection.Dump" Version="1.3.0"/>
<PackageReference Include="xunit.abstractions" Version="2.0.3"/>
</ItemGroup>

</Project>
13 changes: 13 additions & 0 deletions Frank.Testing.TestOutputExtensions/TestOutputTableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using ConsoleTableExt;

namespace Xunit.Abstractions;

public static class TestOutputTableExtensions
{
public static void WriteTable<T>(this ITestOutputHelper outputHelper, IEnumerable<T> source, ConsoleTableBuilderFormat format = ConsoleTableBuilderFormat.Minimal) where T : class =>
outputHelper.WriteLine(ConsoleTableBuilder
.From(source.ToList())
.WithFormat(format)
.Export()
.ToString());
}
64 changes: 64 additions & 0 deletions Frank.Testing.Testcontainers/ContainerRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using DotNet.Testcontainers.Containers;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Frank.Testing.Testcontainers;

public class ContainerRunner<T> : ITestcontainerRunner where T : class, IContainer
{
private readonly ILogger<T> _logger;
private readonly T _container;
private readonly CancellationToken _cancellationToken;
private readonly TimeSpan _timeout;

private CancellationTokenSource? _cancellationTokenSource;

internal ContainerRunner(ILogger<T>? logger, T container, TimeSpan timeout, CancellationToken cancellationToken)
{
_logger = logger ??= new NullLogger<T>();
_container = container ?? throw new ArgumentNullException(nameof(container));
_cancellationToken = cancellationToken;
_timeout = timeout;
}

public async Task StartAsync()
{
_cancellationTokenSource = new CancellationTokenSource(_timeout);
_cancellationToken.Register(() => _cancellationTokenSource.Cancel());
await _container.StartAsync(_cancellationTokenSource.Token);
}

public async Task StopAsync()
{
await _container.StopAsync(_cancellationToken);
await DisposeAsync();
}

public TestcontainersStates GetState() => _container.State;

/// <inheritdoc />
public async Task ExecuteCommandAsync(string command, CancellationToken cancellationToken = default)
{
await _container.ExecAsync(new[] { command }, _cancellationToken);
}

public async Task ExecuteAsync(Func<Task> actionAsync)
{
try
{
await actionAsync();
}
catch (Exception? exception)
{
_logger.LogError(exception, "Failed to execute action in container {ContainerName}", _container.Name);
}
}

/// <inheritdoc />
public async ValueTask DisposeAsync()
{
await _container.DisposeAsync();
_cancellationTokenSource?.Dispose();
}
}
13 changes: 13 additions & 0 deletions Frank.Testing.Testcontainers/Frank.Testing.Testcontainers.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Testcontainers" Version="3.7.0" />
</ItemGroup>

</Project>
16 changes: 16 additions & 0 deletions Frank.Testing.Testcontainers/ITestcontainerRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using DotNet.Testcontainers.Containers;

namespace Frank.Testing.Testcontainers;

public interface ITestcontainerRunner : IAsyncDisposable
{
Task StartAsync();

Task StopAsync();

TestcontainersStates GetState();

Task ExecuteCommandAsync(string command, CancellationToken cancellationToken = default);

Task ExecuteAsync(Func<Task> actionAsync);
}
51 changes: 51 additions & 0 deletions Frank.Testing.Testcontainers/TestContainerRunnerBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using DotNet.Testcontainers.Containers;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Frank.Testing.Testcontainers;

public class TestContainerRunnerBuilder<T> where T : class, IContainer
{
private ILogger<T>? _logger;
private TimeSpan _maxLifetime;
private CancellationToken _cancellationToken;
private Func<IContainer>? _containerFactory;

public TestContainerRunnerBuilder<T> WithLogger(ILogger<T>? logger)
{
_logger = logger;
return this;
}

public TestContainerRunnerBuilder<T> WithMaxLifetime(TimeSpan maxLifetime)
{
_maxLifetime = maxLifetime;
return this;
}

public TestContainerRunnerBuilder<T> WithCancellationToken(CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
return this;
}

public TestContainerRunnerBuilder<T> WithContainerFactory(Func<IContainer>? containerFactory)
{
_containerFactory = containerFactory;
return this;
}

public ITestcontainerRunner Build()
{
_logger ??= new NullLogger<T>();
if (_containerFactory == null)
throw new ArgumentNullException(nameof(_containerFactory));
if (_maxLifetime == default)
_maxLifetime = TimeSpan.FromMinutes(1);
if (_cancellationToken == default)
_cancellationToken = CancellationToken.None;

return new ContainerRunner<IContainer>(_logger, _containerFactory(), _maxLifetime, _cancellationToken);
}
}
1 change: 1 addition & 0 deletions Frank.Testing.Tests/Frank.Testing.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

using Xunit.Abstractions;

namespace Frank.Testing.Tests;
namespace Frank.Testing.Tests.TestOutputExtensionsTests;

[TestSubject(typeof(TestOutputHelperExtensions))]
public class TestOutputHelperExtensionsTests(ITestOutputHelper testOutputHelper)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Xunit.Abstractions;

namespace Frank.Testing.Tests.TestOutputExtensionsTests;

public class TestOutputTableExtensionsTests
{
private readonly ITestOutputHelper _outputHelper;

public TestOutputTableExtensionsTests(ITestOutputHelper outputHelper)
{
_outputHelper = outputHelper;
}

[Fact]
public void ToTable_WithEnumerable_ReturnsTable()
{
// Arrange
var passwords = new[]
{
new { Sha1Hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8", Sha1Prefix = "5BAA6", Sha2Suffix = "1E4C9B93F3F0682250B6CF8331B7EE68FD8", TimesPwned = 3645844 }, new { Sha1Hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8", Sha1Prefix = "5BAA6", Sha2Suffix = "1E4C9B93F3F0682250B6CF8331B7EE68FD8", TimesPwned = 3645844 }, new { Sha1Hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8", Sha1Prefix = "5BAA6", Sha2Suffix = "1E4C9B93F3F0682250B6CF8331B7EE68FD8", TimesPwned = 3645844 },
new { Sha1Hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8", Sha1Prefix = "5BAA6", Sha2Suffix = "1E4C9B93F3F0682250B6CF8331B7EE68FD8", TimesPwned = 364584 },
};

// Act
_outputHelper.WriteTable(passwords);
}
}
6 changes: 6 additions & 0 deletions Frank.Testing.sln
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.ApiTesting",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.EntityFrameworkCore", "Frank.Testing.EntityFrameworkCore\Frank.Testing.EntityFrameworkCore.csproj", "{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frank.Testing.Testcontainers", "Frank.Testing.Testcontainers\Frank.Testing.Testcontainers.csproj", "{13E7B32B-CBA5-4BA3-854B-697B835796F7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -53,5 +55,9 @@ Global
{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A64BD8FC-364F-40E5-A276-F1DEA9D98F67}.Release|Any CPU.Build.0 = Release|Any CPU
{13E7B32B-CBA5-4BA3-854B-697B835796F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13E7B32B-CBA5-4BA3-854B-697B835796F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13E7B32B-CBA5-4BA3-854B-697B835796F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13E7B32B-CBA5-4BA3-854B-697B835796F7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

0 comments on commit c43c04e

Please sign in to comment.