diff --git a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt index f654cc1..5494919 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt @@ -16,13 +16,14 @@ Availability: .NET 9, .NET 8 and .NET Standard 2.0 # New Features - ADDED HostTest class in the Codebelt.Extensions.Xunit.Hosting namespace that represents the non-generic base class from where its generic equivalent should derive (e.g., MinimalHostTest{T}, HostTest{T}, etc.) - ADDED IGenericHostFixture interface in the Codebelt.Extensions.Xunit.Hosting namespace that provides a set of members for configuring the host -- ADDED GenericHostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace that provides a default implementation of the IGenericHostFixture interface +- ADDED GenericHostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace that provides a default implementation of the IGenericHostFixture interface (replaces the legacy HostFixture class) - ADDED GenericHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace that consist of one extension method for the IGenericHostFixture interface: HasValidState - ADDED IMinimalHostFixture interface in the Codebelt.Extensions.Xunit.Hosting namespace that provides a set of members for configuring the host (minimal style) - ADDED MinimalHostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace that provides a default implementation of the IMinimalHostFixture interface - ADDED MinimalHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace that consist of one extension method for the IMinimalHostFixture interface: HasValidState - ADDED MinimalHostTest class in the Codebelt.Extensions.Xunit.Hosting namespace that represents the non-generic base class from where its generic equivalent should derive (e.g., MinimalWebHostTest, {T}, MinimalHostTest{T}, etc.) - ADDED MinimalHostTestFactory class in the Codebelt.Extensions.Xunit.Hosting namespace that provides a set of static methods for IHost unit testing (minimal style) +- EXTENDED ServiceCollectionExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace with one new extension method for the IServiceCollection interface: An overload of AddXunitTestLogging that does not rely on the ITestOutputHelper interface   Version 9.1.3 Availability: .NET 9, .NET 8 and .NET Standard 2.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fd6148..649f0bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,13 +9,13 @@ For more details, please refer to `PackageReleaseNotes.txt` on a per assembly ba ## [10.0.0] - TBD -This major release introduces support for unit testing Minimal APIs and includes numerous breaking changes with valuable learnings from previous 9.0.x releases. These changes aim to ensure greater consistency across the `Codebelt.Extensions.Xunit.Hosting` and `Codebelt.Extensions.Xunit.Hosting.AspNetCore` namespaces. +This major release introduces support for unit testing Minimal APIs and includes numerous breaking changes with valuable learnings from previous 9.1.x releases. These changes aim to ensure greater consistency across the `Codebelt.Extensions.Xunit.Hosting` and `Codebelt.Extensions.Xunit.Hosting.AspNetCore` namespaces. ### Added - HostTest class in the Codebelt.Extensions.Xunit.Hosting namespace that represents the non-generic base class from where its generic equivalent should derive (e.g., MinimalHostTest{T}, HostTest{T}, etc.) - IGenericHostFixture interface in the Codebelt.Extensions.Xunit.Hosting namespace that provides a set of members for configuring the host -- GenericHostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace that provides a default implementation of the IGenericHostFixture interface +- GenericHostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace that provides a default implementation of the IGenericHostFixture interface (replaces the legacy HostFixture class) - GenericHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace that consist of one extension method for the IGenericHostFixture interface: HasValidState - IMinimalHostFixture interface in the Codebelt.Extensions.Xunit.Hosting namespace that provides a set of members for configuring the host (minimal style) - MinimalHostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace that provides a default implementation of the IMinimalHostFixture interface @@ -31,6 +31,8 @@ This major release introduces support for unit testing Minimal APIs and includes - MinimalHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace that consist of one extension method for the IMinimalHostFixture interface: HasValidState - MinimalHostTest class in the Codebelt.Extensions.Xunit.Hosting namespace that represents the non-generic base class from where its generic equivalent should derive (e.g., MinimalWebHostTest, {T}, MinimalHostTest{T}, etc.) - MinimalHostTestFactory class in the Codebelt.Extensions.Xunit.Hosting namespace that provides a set of static methods for IHost unit testing (minimal style) +- ServiceCollectionExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace received one new extension method for the IServiceCollection interface: An overload of AddXunitTestLogging that does not rely on the ITestOutputHelper interface + - This was done to help mitigate those scenarios where the [current design of xUnit v2 and ITestOutputHelper can cause deadlocks](https://github.com/xunit/xunit/discussions/2994), e.g. you can access the xUnit logger but nothing is logged to the test output ### Changed diff --git a/Directory.Packages.props b/Directory.Packages.props index 0270c07..f94a664 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,7 +7,7 @@ - + @@ -24,12 +24,12 @@ - - - - - - + + + + + + @@ -37,6 +37,6 @@ - + \ No newline at end of file diff --git a/src/Codebelt.Extensions.Xunit.Hosting/LoggerExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting/LoggerExtensions.cs index 230526f..c85e32e 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/LoggerExtensions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/LoggerExtensions.cs @@ -13,7 +13,7 @@ namespace Codebelt.Extensions.Xunit.Hosting public static class LoggerExtensions { /// - /// Returns the associated that is provided when settings up services from . + /// Returns the associated that is provided when settings up services from or related. /// /// The from which to retrieve the . /// The category name for messages produced by the -or- null for messages produced by all loggers. @@ -57,7 +57,7 @@ public static ITestStore GetTestStore(this ILogger logger, } /// - /// Returns the associated that is provided when settings up services from . + /// Returns the associated that is provided when settings up services from or related. /// /// The from which to retrieve the . /// Returns an implementation of with all logged entries expressed as . diff --git a/src/Codebelt.Extensions.Xunit.Hosting/ServiceCollectionExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting/ServiceCollectionExtensions.cs index ea06662..d60624c 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/ServiceCollectionExtensions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/ServiceCollectionExtensions.cs @@ -11,6 +11,27 @@ namespace Codebelt.Extensions.Xunit.Hosting /// public static class ServiceCollectionExtensions { + /// + /// Adds a unit test optimized implementation of logging to the collection. + /// + /// The to extend. + /// 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. + /// + public static IServiceCollection AddXunitTestLogging(this IServiceCollection services, LogLevel minimumLevel = LogLevel.Trace) + { + if (services == null) { throw new ArgumentNullException(nameof(services)); } + services.AddLogging(builder => + { + builder.SetMinimumLevel(minimumLevel); + builder.AddProvider(new XunitTestLoggerProvider()); + }); + + return services; + } + /// /// Adds a unit test optimized implementation of output logging to the collection. /// @@ -22,14 +43,14 @@ public static class ServiceCollectionExtensions /// cannot be null -or- /// cannot be null. /// - public static IServiceCollection AddXunitTestLogging(this IServiceCollection services, ITestOutputHelper output, LogLevel minimumLevel = LogLevel.Trace) - { + public static IServiceCollection AddXunitTestLogging(this IServiceCollection services, ITestOutputHelper output, LogLevel minimumLevel = LogLevel.Trace) + { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (output == null) { throw new ArgumentNullException(nameof(output)); } if (services.Any(sd => sd.ServiceType == typeof(ITestOutputHelperAccessor))) { - services.AddLogging(builder => - { + services.AddLogging(builder => + { builder.SetMinimumLevel(minimumLevel); builder.Services.AddSingleton(provider => { @@ -37,19 +58,19 @@ public static IServiceCollection AddXunitTestLogging(this IServiceCollection ser accessor.TestOutput = output; return new XunitTestLoggerProvider(accessor); }); - }); + }); } else { - services.AddLogging(builder => - { - builder.SetMinimumLevel(minimumLevel); - builder.AddProvider(new XunitTestLoggerProvider(output)); - }); + services.AddLogging(builder => + { + builder.SetMinimumLevel(minimumLevel); + builder.AddProvider(new XunitTestLoggerProvider(output)); + }); } - return services; - } - + return services; + } + /// /// Adds a default implementation of to the collection. /// diff --git a/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLogger.cs b/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLogger.cs index aae9ec2..66d7e5c 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLogger.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLogger.cs @@ -41,7 +41,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except } else { - _output.WriteLine(message); + _output?.WriteLine(message); } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs b/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs index 8ec44fe..00a52a1 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs @@ -12,6 +12,10 @@ internal sealed class XunitTestLoggerProvider : InMemoryTestStore Assert.Equal("Information: SUT", entry.ToString()), entry => Assert.Equal("Warning: SUT", entry.ToString()), entry => Assert.Equal("Information: Unique message for logger2.", entry.ToString())); + } + + [Fact] + public void AddXunitTestLogging_ShouldAddXunitTestLoggingWithTestOutput() + { + var services = new ServiceCollection(); + services.AddXunitTestLogging(TestOutput); + + var provider = services.BuildServiceProvider(); + + var logger1 = provider.GetRequiredService>(); + var loggerStore1 = logger1.GetTestStore(); + var loggerStore1Duplicate = logger1.GetTestStore(typeof(ServiceCollectionExtensions).FullName!.ToLowerInvariant()); + + var logger2 = provider.GetRequiredService>(); + var loggerStore2 = logger2.GetTestStore(null); // all loggers + + logger1.LogCritical("SUT"); + logger1.LogTrace("SUT"); + logger1.LogDebug("SUT"); + logger1.LogError("SUT"); + logger1.LogInformation("SUT"); + logger1.LogWarning("SUT"); + + logger2.LogInformation("Unique message for logger2."); + + Assert.Throws(() => logger2.GetTestStore("InvalidValue")); + Assert.Equal(loggerStore1, loggerStore1Duplicate); + + Assert.NotNull(logger1); + Assert.NotNull(loggerStore1); + + Assert.NotNull(logger2); + Assert.NotNull(loggerStore2); + + Assert.Equal(6, loggerStore1.Query().Count()); + Assert.Equal(7, loggerStore2.Query().Count()); + + Assert.Collection(loggerStore1.Query(), + entry => Assert.Equal("Critical: SUT", entry.ToString()), + entry => Assert.Equal("Trace: SUT", entry.ToString()), + entry => Assert.Equal("Debug: SUT", entry.ToString()), + entry => Assert.Equal("Error: SUT", entry.ToString()), + entry => Assert.Equal("Information: SUT", entry.ToString()), + entry => Assert.Equal("Warning: SUT", entry.ToString())); + + Assert.Collection(loggerStore2.Query(), + entry => Assert.Equal("Critical: SUT", entry.ToString()), + entry => Assert.Equal("Trace: SUT", entry.ToString()), + entry => Assert.Equal("Debug: SUT", entry.ToString()), + entry => Assert.Equal("Error: SUT", entry.ToString()), + entry => Assert.Equal("Information: SUT", entry.ToString()), + entry => Assert.Equal("Warning: SUT", entry.ToString()), + entry => Assert.Equal("Information: Unique message for logger2.", entry.ToString())); } } } diff --git a/testenvironments.json b/testenvironments.json index e3ebb1b..cb17045 100644 --- a/testenvironments.json +++ b/testenvironments.json @@ -9,7 +9,7 @@ { "name": "Docker-Ubuntu", "type": "docker", - "dockerImage": "gimlichael/ubuntu-testrunner:mono-net8.0.407-9.0.202" + "dockerImage": "gimlichael/ubuntu-testrunner:mono-net8.0.408-9.0.203" } ] }