diff --git a/.nuget/Cuemon.Extensions.Xunit.Hosting/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Xunit.Hosting/PackageReleaseNotes.txt index 6ae8375d5..21ab2ae7b 100644 --- a/.nuget/Cuemon.Extensions.Xunit.Hosting/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Xunit.Hosting/PackageReleaseNotes.txt @@ -6,6 +6,7 @@ Availability: .NET 9, .NET 8, .NET 6 and .NET Standard 2.0   # New Features - ADDED ServiceProviderExtensions class in the Cuemon.Extensions.Xunit.Hosting namespace that consist of one extension method for the IServiceProvider interface: GetRequiredScopedService +- EXTENDED ServiceCollectionExtensions class in the Cuemon.Extensions.Xunit.Hosting namespace with three new extension methods for the IServiceCollection interface: AddXunitTestOutputHelperAccessor, AddXunitTestOutputHelperAccessor{T} and an overload of AddXunitTestOutputHelperAccessor   Version 8.3.2 Availability: .NET 8, .NET 6 and .NET Standard 2.0 diff --git a/.nuget/Cuemon.Extensions.Xunit/PackageReleaseNotes.txt b/.nuget/Cuemon.Extensions.Xunit/PackageReleaseNotes.txt index 572ceda20..63c7d6ceb 100644 --- a/.nuget/Cuemon.Extensions.Xunit/PackageReleaseNotes.txt +++ b/.nuget/Cuemon.Extensions.Xunit/PackageReleaseNotes.txt @@ -7,6 +7,10 @@ Availability: .NET 9, .NET 8, .NET 6 and .NET Standard 2.0 # Improvements - EXTENDED TestOutputHelperExtensions class in the Cuemon.Extensions.Xunit namespace with an additional overloaded method: WriteLines   +# New Features +- ADDED ITestOutputHelperAccessor interface in the Cuemon.Extensions.Xunit namespace that provides access to the ITestOutputHelper instance +- ADDED TestOutputHelperAccessor class in the Cuemon.Extensions.Xunit namespace that provides a default implementation of the ITestOutputHelper interface +  Version 8.3.2 Availability: .NET 8, .NET 6 and .NET Standard 2.0   diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b0f6f57..e66caa308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ New features: - FailureConverter class in the Cuemon.Extensions.Text.Json.Converters namespace to convert FailureConverter to JSON - FailureConverter class in the Cuemon.Xml.Serialization.Converters namespace to convert FailureConverter to XML - Support for System.Threading.Lock object that targets TFMs prior to .NET 9 (credits to Mark Cilia Vincenti, https://github.com/MarkCiliaVincenti/Backport.System.Threading.Lock) +- ITestOutputHelperAccessor interface in the Cuemon.Extensions.Xunit namespace that provides access to the ITestOutputHelper instance +- TestOutputHelperAccessor class in the Cuemon.Extensions.Xunit namespace that provides a default implementation of the ITestOutputHelper interface +- ServiceProviderExtensions class in the Cuemon.Extensions.Xunit.Hosting namespace that consist of one extension method for the IServiceProvider interface: GetRequiredScopedService +- ServiceCollectionExtensions class in the Cuemon.Extensions.Xunit.Hosting namespace was extended with three new extension methods for the IServiceCollection interface: AddXunitTestOutputHelperAccessor, AddXunitTestOutputHelperAccessor{T} and an overload of AddXunitTestOutputHelperAccessor ### Changed diff --git a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj index b519913ee..a447e3092 100644 --- a/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj +++ b/src/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json/Cuemon.Extensions.AspNetCore.Mvc.Formatters.Newtonsoft.Json.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Cuemon.Extensions.DependencyInjection/Cuemon.Extensions.DependencyInjection.csproj b/src/Cuemon.Extensions.DependencyInjection/Cuemon.Extensions.DependencyInjection.csproj index c879eb7ea..d8ad566e2 100644 --- a/src/Cuemon.Extensions.DependencyInjection/Cuemon.Extensions.DependencyInjection.csproj +++ b/src/Cuemon.Extensions.DependencyInjection/Cuemon.Extensions.DependencyInjection.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/src/Cuemon.Extensions.Hosting/Cuemon.Extensions.Hosting.csproj b/src/Cuemon.Extensions.Hosting/Cuemon.Extensions.Hosting.csproj index d30d380eb..6a2cbd0ef 100644 --- a/src/Cuemon.Extensions.Hosting/Cuemon.Extensions.Hosting.csproj +++ b/src/Cuemon.Extensions.Hosting/Cuemon.Extensions.Hosting.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Cuemon.Extensions.Net/Cuemon.Extensions.Net.csproj b/src/Cuemon.Extensions.Net/Cuemon.Extensions.Net.csproj index 14dab98e8..460d7e81d 100644 --- a/src/Cuemon.Extensions.Net/Cuemon.Extensions.Net.csproj +++ b/src/Cuemon.Extensions.Net/Cuemon.Extensions.Net.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Cuemon.Extensions.Text.Json/Cuemon.Extensions.Text.Json.csproj b/src/Cuemon.Extensions.Text.Json/Cuemon.Extensions.Text.Json.csproj index 8cd21616c..ec195212d 100644 --- a/src/Cuemon.Extensions.Text.Json/Cuemon.Extensions.Text.Json.csproj +++ b/src/Cuemon.Extensions.Text.Json/Cuemon.Extensions.Text.Json.csproj @@ -17,7 +17,7 @@ - + diff --git a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj index b85bf0488..ffed3069f 100644 --- a/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj +++ b/src/Cuemon.Extensions.Xunit.Hosting.AspNetCore/Cuemon.Extensions.Xunit.Hosting.AspNetCore.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/Cuemon.Extensions.Xunit.Hosting/Cuemon.Extensions.Xunit.Hosting.csproj b/src/Cuemon.Extensions.Xunit.Hosting/Cuemon.Extensions.Xunit.Hosting.csproj index 1b5e7f71c..ee24a2cf2 100644 --- a/src/Cuemon.Extensions.Xunit.Hosting/Cuemon.Extensions.Xunit.Hosting.csproj +++ b/src/Cuemon.Extensions.Xunit.Hosting/Cuemon.Extensions.Xunit.Hosting.csproj @@ -11,11 +11,11 @@ - - - - - + + + + + diff --git a/src/Cuemon.Extensions.Xunit.Hosting/ServiceCollectionExtensions.cs b/src/Cuemon.Extensions.Xunit.Hosting/ServiceCollectionExtensions.cs index 7552e0131..60ac253e1 100644 --- a/src/Cuemon.Extensions.Xunit.Hosting/ServiceCollectionExtensions.cs +++ b/src/Cuemon.Extensions.Xunit.Hosting/ServiceCollectionExtensions.cs @@ -32,5 +32,55 @@ public static IServiceCollection AddXunitTestLogging(this IServiceCollection ser }); return services; } + + /// + /// Adds a unit test optimized implementation of output logging to the collection. + /// + /// The to extend. + /// The that provides access to the output for the logging. + /// The that specifies the minimum level to include for the logging. + /// A reference to so that additional configuration calls can be chained. + /// + /// cannot be null -or- + /// cannot be null. + /// + public static IServiceCollection AddXunitTestLogging(this IServiceCollection services, ITestOutputHelperAccessor accessor, LogLevel minimumLevel = LogLevel.Trace) + { + Validator.ThrowIfNull(services); + Validator.ThrowIfNull(accessor); + services.AddLogging(builder => + { + builder.SetMinimumLevel(minimumLevel); + builder.AddProvider(new XunitTestLoggerProvider(accessor)); + }); + return services; + } + + /// + /// Adds a default implementation of to the collection. + /// + /// The to extend. + /// A reference to so that additional configuration calls can be chained. + public static IServiceCollection AddXunitTestOutputHelperAccessor(this IServiceCollection services) + { + services.AddXunitTestOutputHelperAccessor(); + return services; + } + + /// + /// Adds a specified implementation of to the collection. + /// + /// The type of the implementation of . + /// The to extend. + /// A reference to so that additional configuration calls can be chained. + /// + /// cannot be null. + /// + public static IServiceCollection AddXunitTestOutputHelperAccessor(this IServiceCollection services) where T : class, ITestOutputHelperAccessor + { + Validator.ThrowIfNull(services); + services.AddSingleton(); + return services; + } } } diff --git a/src/Cuemon.Extensions.Xunit.Hosting/XunitTestLogger.cs b/src/Cuemon.Extensions.Xunit.Hosting/XunitTestLogger.cs index eea1d18cd..8af387b57 100644 --- a/src/Cuemon.Extensions.Xunit.Hosting/XunitTestLogger.cs +++ b/src/Cuemon.Extensions.Xunit.Hosting/XunitTestLogger.cs @@ -7,6 +7,7 @@ namespace Cuemon.Extensions.Xunit.Hosting { internal class XunitTestLogger : InMemoryTestStore, ILogger, IDisposable { + private readonly ITestOutputHelperAccessor _accessor; private readonly ITestOutputHelper _output; public XunitTestLogger(ITestOutputHelper output) @@ -14,13 +15,25 @@ public XunitTestLogger(ITestOutputHelper output) _output = output; } + public XunitTestLogger(ITestOutputHelperAccessor accessor) + { + _accessor = accessor; + } + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { var builder = new StringBuilder($"{logLevel}: {formatter(state, exception)}"); if (exception != null) { builder.AppendLine().Append(exception).AppendLine(); } var message = builder.ToString(); - _output.WriteLine(message); + if (_accessor != null) + { + _accessor.TestOutput.WriteLine(message); + } + else + { + _output.WriteLine(message); + } Add(new XunitTestLoggerEntry(logLevel, eventId, message)); } diff --git a/src/Cuemon.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs b/src/Cuemon.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs index 86e77b574..a3c1cdc8e 100644 --- a/src/Cuemon.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs +++ b/src/Cuemon.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs @@ -7,6 +7,7 @@ namespace Cuemon.Extensions.Xunit.Hosting internal class XunitTestLoggerProvider : ILoggerProvider { private readonly ConcurrentDictionary _loggers = new(); + private readonly ITestOutputHelperAccessor _accessor; private readonly ITestOutputHelper _output; public XunitTestLoggerProvider(ITestOutputHelper output) @@ -14,9 +15,16 @@ public XunitTestLoggerProvider(ITestOutputHelper output) _output = output; } + public XunitTestLoggerProvider(ITestOutputHelperAccessor accessor) + { + _accessor = accessor; + } + public ILogger CreateLogger(string categoryName) { - return _loggers.GetOrAdd(categoryName, s => new XunitTestLogger(_output)); + return _loggers.GetOrAdd(categoryName, _ => _accessor != null + ? new XunitTestLogger(_accessor) + : new XunitTestLogger(_output)); } public void Dispose() diff --git a/src/Cuemon.Extensions.Xunit/ITestOutputHelperAccessor.cs b/src/Cuemon.Extensions.Xunit/ITestOutputHelperAccessor.cs new file mode 100644 index 000000000..41641ee32 --- /dev/null +++ b/src/Cuemon.Extensions.Xunit/ITestOutputHelperAccessor.cs @@ -0,0 +1,16 @@ +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Xunit +{ + /// + /// Provides an interface for accessing the instance. + /// + public interface ITestOutputHelperAccessor + { + /// + /// Gets or sets the instance used for outputting test results. + /// + /// The instance. + ITestOutputHelper TestOutput { get; set; } + } +} diff --git a/src/Cuemon.Extensions.Xunit/TestOutputHelperAccessor.cs b/src/Cuemon.Extensions.Xunit/TestOutputHelperAccessor.cs new file mode 100644 index 000000000..55a6c546d --- /dev/null +++ b/src/Cuemon.Extensions.Xunit/TestOutputHelperAccessor.cs @@ -0,0 +1,32 @@ +using System.Threading; +using Xunit.Abstractions; + +namespace Cuemon.Extensions.Xunit +{ + /// + /// Provides a default implementation of the interface. + /// + public class TestOutputHelperAccessor : ITestOutputHelperAccessor + { + private static readonly AsyncLocal Current = new(); + + /// + /// Initializes a new instance of the class with the specified instance. + /// + /// The instance to be used for outputting test results. + public TestOutputHelperAccessor(ITestOutputHelper output = null) + { + Current.Value = output; + } + + /// + /// Gets or sets the instance used for outputting test results. + /// + /// The instance. + public ITestOutputHelper TestOutput + { + get => Current.Value; + set => Current.Value = value; + } + } +} diff --git a/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj b/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj index a9d822e51..46c6044ee 100644 --- a/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj +++ b/test/Cuemon.Data.Tests/Cuemon.Data.Tests.csproj @@ -26,15 +26,15 @@ - + - + - + diff --git a/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs index 059e3bd1b..50c8f8e14 100644 --- a/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs +++ b/test/Cuemon.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Xunit; using Xunit.Abstractions; @@ -21,6 +22,8 @@ public AspNetCoreHostTestTest(AspNetCoreHostFixture hostFixture, ITestOutputHelp { _pipeline = hostFixture.Application; _provider = hostFixture.ServiceProvider; + + _provider.GetRequiredService().TestOutput = output; } [Fact] @@ -30,7 +33,7 @@ public async Task ShouldHaveResultOfBoolMiddlewareInBody() var options = _provider.GetRequiredService>(); var pipeline = _pipeline.Build(); - Assert.Equal("Hello awesome developers!", context.Response.Body.ToEncodedString(o => o.LeaveOpen = true)); + Assert.Equal("Hello awesome developers!", context!.Response.Body.ToEncodedString(o => o.LeaveOpen = true)); await pipeline(context); @@ -44,8 +47,16 @@ public async Task ShouldHaveResultOfBoolMiddlewareInBody() Assert.False(options.Value.F); } + [Fact] + public void ShouldLogToXunitTestLogging() + { + var logger = _pipeline.ApplicationServices.GetRequiredService>(); + logger.LogInformation("Hello from {0}", nameof(ShouldLogToXunitTestLogging)); + } + public override void ConfigureApplication(IApplicationBuilder app) { + app.ApplicationServices.GetRequiredService>().LogInformation(nameof(ConfigureApplication)); app.UseMiddleware(); } @@ -58,6 +69,8 @@ public override void ConfigureServices(IServiceCollection services) o.C = true; o.E = true; }); + services.AddXunitTestOutputHelperAccessor(); + services.AddXunitTestLogging(new TestOutputHelperAccessor(TestOutput)); } } -} \ No newline at end of file +} diff --git a/testenvironments.json b/testenvironments.json index 682fcd417..b5827e2ef 100644 --- a/testenvironments.json +++ b/testenvironments.json @@ -9,7 +9,7 @@ { "name": "Docker-Ubuntu", "type": "docker", - "dockerImage": "gimlichael/ubuntu-testrunner:net6.0.425-net8.0.401-9.0.100-preview.7.24407.12" + "dockerImage": "gimlichael/ubuntu-testrunner:net6.0.425-net8.0.401-9.0.100-rc.1.24452.12" } ] }