From 3370eee94988aaf7b77996aa85d40b2511fa7416 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 30 Mar 2025 22:35:35 +0200 Subject: [PATCH 01/17] :safety_vest: refactored from internal to public HasValidState (for IHostFixture and derived) --- .../AspNetCoreHostFixtureExtensions.cs | 20 ++++++++++++++--- .../HostFixtureExtensions.cs | 22 ++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs index 9db5138..38e1198 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs @@ -1,10 +1,24 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore { - internal static class AspNetCoreHostFixtureExtensions + /// + /// Extension methods for the interface. + /// + public static class AspNetCoreHostFixtureExtensions { - internal static bool HasValidState(this IAspNetCoreHostFixture fixture) + /// + /// Determines whether the specified has a valid state. + /// + /// The to check. + /// true if the specified has a valid state; otherwise, false. + /// + /// A valid state is defined as having non-null values for the following properties: + /// , , + /// , and . + /// + public static bool HasValidState(this IAspNetCoreHostFixture fixture) { - return fixture.ConfigureServicesCallback != null && fixture.Host != null && fixture.ServiceProvider != null && fixture.Application != null && fixture.ConfigureHostCallback != null; + var hasValidState = ((IHostFixture)fixture).HasValidState(); + return hasValidState && fixture.Application != null; } } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs index c10c010..00873f0 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs @@ -1,10 +1,26 @@ namespace Codebelt.Extensions.Xunit.Hosting { - internal static class HostFixtureExtensions + /// + /// Extension methods for the interface. + /// + public static class HostFixtureExtensions { - internal static bool HasValidState(this IHostFixture fixture) + /// + /// Determines whether the specified has a valid state. + /// + /// The to check. + /// true if the specified has a valid state; otherwise, false. + /// + /// A valid state is defined as having non-null values for the following properties: + /// , , + /// and . + /// + public static bool HasValidState(this IHostFixture fixture) { - return fixture.ConfigureServicesCallback != null && fixture.Host != null && fixture.ServiceProvider != null && fixture.ConfigureHostCallback != null; + return fixture.ConfigureServicesCallback != null && + fixture.Host != null && + fixture.ServiceProvider != null && + fixture.ConfigureHostCallback != null; } } } From 1c3d1b41effe845a5dd745273328b4e35b307f76 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 30 Mar 2025 22:45:12 +0200 Subject: [PATCH 02/17] :recycle: reduced code redundancies, applied housekeeping and removed virtual ctor call --- .../AspNetCoreHostFixture.cs | 9 ++--- .../AspNetCoreHostTest.cs | 20 ----------- .../HostFixture.cs | 35 ++++++++++++------- .../HostTest.cs | 21 +---------- .../IHostFixture.cs | 17 ++++----- 5 files changed, 33 insertions(+), 69 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs index 3e7c912..3d26e8e 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs @@ -90,12 +90,9 @@ public override void ConfigureHost(Test hostTest) ConfigureHostCallback(hb); - var host = hb.Build(); - Task.Run(() => host.StartAsync().ConfigureAwait(false)) - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(); - Host = host; + Host = hb.Build(); + + StartConfiguredHost(); } /// diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs index 737ae3d..3dbd6d3 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs @@ -23,26 +23,6 @@ protected AspNetCoreHostTest(T hostFixture, ITestOutputHelper output = null, Typ { } - /// - /// Initializes the specified host fixture. - /// - /// The host fixture to initialize. - protected override void InitializeHostFixture(T hostFixture) - { - if (!hostFixture.HasValidState()) - { - hostFixture.ConfigureCallback = Configure; - hostFixture.ConfigureHostCallback = ConfigureHost; - hostFixture.ConfigureServicesCallback = ConfigureServices; - hostFixture.ConfigureApplicationCallback = ConfigureApplication; - hostFixture.ConfigureHost(this); - } - Host = hostFixture.Host; - ServiceProvider = hostFixture.Host.Services; - Application = hostFixture.Application; - Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); - } - /// /// Gets the initialized by the . /// diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs index 5fa84fd..02f92be 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs @@ -59,21 +59,30 @@ public virtual void ConfigureHost(Test hostTest) }); #if NET9_0_OR_GREATER - hb.UseDefaultServiceProvider(o => - { - o.ValidateOnBuild = true; - o.ValidateScopes = true; - }); + hb.UseDefaultServiceProvider(o => + { + o.ValidateOnBuild = true; + o.ValidateScopes = true; + }); #endif ConfigureHostCallback(hb); - var host = hb.Build(); - Task.Run(() => host.StartAsync().ConfigureAwait(false)) + Host = hb.Build(); + + StartConfiguredHost(); + } + + /// + /// Starts the by initialized . + /// + /// is responsible for configuring and setting the property. + protected virtual void StartConfiguredHost() + { + Task.Run(() => Host.StartAsync().ConfigureAwait(false)) // this was done to reduce the risk of deadlocks (https://www.strathweb.com/2021/05/the-curious-case-of-asp-net-core-integration-test-deadlock/) .ConfigureAwait(false) .GetAwaiter() .GetResult(); - Host = host; } /// @@ -179,11 +188,11 @@ protected virtual async ValueTask OnDisposeManagedResourcesAsync() } } #else - protected virtual ValueTask OnDisposeManagedResourcesAsync() - { - OnDisposeManagedResources(); - return default; - } + protected virtual ValueTask OnDisposeManagedResourcesAsync() + { + OnDisposeManagedResources(); + return default; + } #endif /// diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs index eea804a..7fa52a1 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs @@ -25,25 +25,6 @@ public abstract class HostTest : Test, IClassFixture where T : class, IHos protected HostTest(T hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(output, callerType) { if (hostFixture == null) { throw new ArgumentNullException(nameof(hostFixture)); } - InitializeHostFixture(hostFixture); - } - - /// - /// Initializes the specified host fixture. - /// - /// The host fixture to initialize. - protected virtual void InitializeHostFixture(T hostFixture) - { - if (!hostFixture.HasValidState()) - { - hostFixture.ConfigureCallback = Configure; - hostFixture.ConfigureHostCallback = ConfigureHost; - hostFixture.ConfigureServicesCallback = ConfigureServices; - hostFixture.ConfigureHost(this); - } - Host = hostFixture.Host; - ServiceProvider = hostFixture.ServiceProvider; - Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); } /// @@ -83,7 +64,7 @@ public IHostEnvironment HostingEnvironment /// /// The initialized by the . /// The initialized by the . - public virtual void Configure(IConfiguration configuration, IHostEnvironment environment) + public void Configure(IConfiguration configuration, IHostEnvironment environment) { Configuration = configuration; HostingEnvironment = environment; diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs index 4b38001..3a17b74 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs @@ -5,15 +5,6 @@ namespace Codebelt.Extensions.Xunit.Hosting { - public partial interface IHostFixture - { - /// - /// Gets or sets the delegate that adds configuration and environment information to a . - /// - /// The delegate that adds configuration and environment information to a . - Action ConfigureCallback { get; set; } - } - /// /// Provides a way to use Microsoft Dependency Injection in unit tests. /// @@ -23,7 +14,7 @@ public partial interface IHostFixture /// /// /// - public partial interface IHostFixture : IServiceTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest, IDisposable, IAsyncDisposable + public interface IHostFixture : IServiceTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest, IDisposable, IAsyncDisposable { /// /// Gets or sets the delegate that adds services to the container. @@ -43,5 +34,11 @@ public partial interface IHostFixture : IServiceTest, IHostTest, IConfigurationT /// The object that inherits from . /// was added to support those cases where the caller is required in the host configuration. void ConfigureHost(Test hostTest); + + /// + /// Gets or sets the delegate that adds configuration and environment information to a . + /// + /// The delegate that adds configuration and environment information to a . + Action ConfigureCallback { get; set; } } } From 678de203ab6618ed3391757b910667283d962072 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 30 Mar 2025 22:46:38 +0200 Subject: [PATCH 03/17] :sparkles: added a way to choose between non-block and blocking AspNet fixture (latter allows for Assert.Throws*) --- .../BlockingAspNetCoreHostFixture.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingAspNetCoreHostFixture.cs diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingAspNetCoreHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingAspNetCoreHostFixture.cs new file mode 100644 index 0000000..7cb6b7c --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingAspNetCoreHostFixture.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Extends the default implementation of the interface to be synchronous e.g., blocking where exceptions can be captured. + /// + /// + public class BlockingAspNetCoreHostFixture : AspNetCoreHostFixture + { + /// + /// Starts the by initialized . + /// + /// is responsible for configuring and setting the property. + protected override void StartConfiguredHost() + { + Host.Start(); + } + } +} From ebc15d6c2e1d0abee24183044cb2a8142284c87c Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 30 Mar 2025 22:54:28 +0200 Subject: [PATCH 04/17] :building_construction: refactored to favor DIP --- .../GlobalSuppressions.cs | 11 +++ .../WebHostTest.cs | 29 +++----- .../WebHostTestFactory.cs | 70 +++++++++++++++++-- .../GenericHostTest.cs | 27 +++---- .../GenericHostTestFactory.cs | 30 +++++++- .../GlobalSuppressions.cs | 2 + 6 files changed, 123 insertions(+), 46 deletions(-) create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/GlobalSuppressions.cs diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/GlobalSuppressions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/GlobalSuppressions.cs new file mode 100644 index 0000000..d969af7 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Blocker Code Smell", "S3427:Method overloads with default parameter values should not overlap", Justification = "Avoid bumping major version by providing an extra overloaded member as optional argument.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostTestFactory.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.AspNetCore.Builder.IApplicationBuilder},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},Codebelt.Extensions.Xunit.Hosting.AspNetCore.IAspNetCoreHostFixture)~Codebelt.Extensions.Xunit.Hosting.AspNetCore.IWebHostTest")] +[assembly: SuppressMessage("Blocker Code Smell", "S3427:Method overloads with default parameter values should not overlap", Justification = "Avoid bumping major version by providing an extra overloaded member as optional argument.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostTestFactory.CreateWithHostBuilderContext(System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.AspNetCore.Builder.IApplicationBuilder},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},Codebelt.Extensions.Xunit.Hosting.AspNetCore.IAspNetCoreHostFixture)~Codebelt.Extensions.Xunit.Hosting.AspNetCore.IWebHostTest")] +[assembly: SuppressMessage("Blocker Code Smell", "S3427:Method overloads with default parameter values should not overlap", Justification = "Avoid bumping major version by providing an extra overloaded member as optional argument.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostTestFactory.RunAsync(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.AspNetCore.Builder.IApplicationBuilder},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},System.Func{System.Net.Http.HttpClient,System.Threading.Tasks.Task{System.Net.Http.HttpResponseMessage}},Codebelt.Extensions.Xunit.Hosting.AspNetCore.IAspNetCoreHostFixture)~System.Threading.Tasks.Task{System.Net.Http.HttpResponseMessage}")] +[assembly: SuppressMessage("Blocker Code Smell", "S3427:Method overloads with default parameter values should not overlap", Justification = "Avoid bumping major version by providing an extra overloaded member as optional argument.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostTestFactory.RunWithHostBuilderContextAsync(System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.AspNetCore.Builder.IApplicationBuilder},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},System.Func{System.Net.Http.HttpClient,System.Threading.Tasks.Task{System.Net.Http.HttpResponseMessage}},Codebelt.Extensions.Xunit.Hosting.AspNetCore.IAspNetCoreHostFixture)~System.Threading.Tasks.Task{System.Net.Http.HttpResponseMessage}")] diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs index b31c050..b1c9f8d 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs @@ -5,7 +5,7 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore { - internal sealed class WebHostTest : AspNetCoreHostTest, IWebHostTest + internal sealed class WebHostTest : AspNetCoreHostTest, IWebHostTest { private readonly Action _pipelineConfigurator; private readonly Action _serviceConfigurator; @@ -14,30 +14,24 @@ internal sealed class WebHostTest : AspNetCoreHostTest, I private readonly Action _hostConfigurator; private HostBuilderContext _hostBuilderContext; - internal WebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, AspNetCoreHostFixture hostFixture) : base(hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + internal WebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, IAspNetCoreHostFixture hostFixture) : base(hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) { _serviceConfigurator = serviceConfigurator; _pipelineConfigurator = pipelineConfigurator; _hostConfigurator = hostConfigurator; - if (!hostFixture.HasValidState()) - { - hostFixture.ConfigureHostCallback = ConfigureHost; - hostFixture.ConfigureCallback = Configure; - hostFixture.ConfigureServicesCallback = ConfigureServices; - hostFixture.ConfigureApplicationCallback = ConfigureApplication; - hostFixture.ConfigureHost(this); - } - Host = hostFixture.Host; - ServiceProvider = hostFixture.Host.Services; - Application = hostFixture.Application; - Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + InitializeHostFixture(hostFixture); } - internal WebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, AspNetCoreHostFixture hostFixture) : base(hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + internal WebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, IAspNetCoreHostFixture hostFixture) : base(hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) { _serviceConfiguratorWithContext = serviceConfigurator; _pipelineConfiguratorWithContext = pipelineConfigurator; _hostConfigurator = hostConfigurator; + InitializeHostFixture(hostFixture); + } + + private void InitializeHostFixture(IAspNetCoreHostFixture hostFixture) + { if (!hostFixture.HasValidState()) { hostFixture.ConfigureHostCallback = ConfigureHost; @@ -52,11 +46,6 @@ internal WebHostTest(Action serviceConfi Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); } - protected override void InitializeHostFixture(AspNetCoreHostFixture hostFixture) - { - // intentionally left blank due to constructor initialization (need to refactor this "call from virtual method" challenge) - } - public override void ConfigureApplication(IApplicationBuilder app) { _pipelineConfigurator?.Invoke(app); diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTestFactory.cs index 0a2af96..8ce2e82 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTestFactory.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTestFactory.cs @@ -21,9 +21,10 @@ public static class WebHostTestFactory /// The which may be configured. /// The which may be configured. /// An instance of an implementation. + [Obsolete("This method is obsolete and will be removed in a future version. Use Create(Action, Action, Action, IAspNetCoreHostFixture) instead.")] public static IWebHostTest Create(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null) { - return new WebHostTest(serviceSetup, pipelineSetup, hostSetup, new AspNetCoreHostFixture()); + return Create(serviceSetup, pipelineSetup, hostSetup, null); } /// @@ -32,10 +33,51 @@ public static IWebHostTest Create(Action serviceSetup = null /// The which may be configured. /// The which may be configured. /// The which may be configured. + /// An optional implementation to use instead of the default instance. /// An instance of an implementation. + public static IWebHostTest Create(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, IAspNetCoreHostFixture hostFixture = null) + { + return new WebHostTest(serviceSetup, pipelineSetup, hostSetup, hostFixture ?? new AspNetCoreHostFixture()); + } + + /// + /// Creates and returns an implementation. + /// + /// The which may be configured. + /// The which may be configured. + /// The which may be configured. + /// An instance of an implementation. + [Obsolete("This method is obsolete and will be removed in a future version. Use CreateWithHostBuilderContext(Action, Action, Action, IAspNetCoreHostFixture) instead.")] public static IWebHostTest CreateWithHostBuilderContext(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null) { - return new WebHostTest(serviceSetup, pipelineSetup, hostSetup, new AspNetCoreHostFixture()); + return CreateWithHostBuilderContext(serviceSetup, pipelineSetup, hostSetup, null); + } + + /// + /// Creates and returns an implementation. + /// + /// The which may be configured. + /// The which may be configured. + /// The which may be configured. + /// An optional implementation to use instead of the default instance. + /// An instance of an implementation. + public static IWebHostTest CreateWithHostBuilderContext(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, IAspNetCoreHostFixture hostFixture = null) + { + return new WebHostTest(serviceSetup, pipelineSetup, hostSetup, hostFixture ?? new AspNetCoreHostFixture()); + } + + /// + /// Runs a middleware and returns an for making HTTP requests to the test server. + /// + /// The which may be configured. + /// The which may be configured. + /// The which may be configured. + /// The function delegate that creates a from the . Default is a GET request to the root URL ("/"). + /// A that represents the asynchronous operation. The task result contains the for the test server. + [Obsolete("This method is obsolete and will be removed in a future version. Use RunAsync(Action, Action, Action, Func>, IAspNetCoreHostFixture) instead.")] + public static Task RunAsync(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, Func> responseFactory = null) + { + return RunAsync(serviceSetup, pipelineSetup, hostSetup, responseFactory, null); } /// @@ -45,10 +87,11 @@ public static IWebHostTest CreateWithHostBuilderContext(ActionThe which may be configured. /// The which may be configured. /// The function delegate that creates a from the . Default is a GET request to the root URL ("/"). + /// An optional implementation to use instead of the default instance. /// A that represents the asynchronous operation. The task result contains the for the test server. - public static async Task RunAsync(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, Func> responseFactory = null) + public static async Task RunAsync(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, Func> responseFactory = null, IAspNetCoreHostFixture hostFixture = null) { - using var client = Create(serviceSetup, pipelineSetup, hostSetup).Host.GetTestClient(); + using var client = Create(serviceSetup, pipelineSetup, hostSetup, hostFixture).Host.GetTestClient(); return await client.ToHttpResponseMessageAsync(responseFactory).ConfigureAwait(false); } @@ -60,9 +103,24 @@ public static async Task RunAsync(ActionThe which may be configured. /// The function delegate that creates a from the . Default is a GET request to the root URL ("/"). /// A that represents the asynchronous operation. The task result contains the for the test server. - public static async Task RunWithHostBuilderContextAsync(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, Func> responseFactory = null) + [Obsolete("This method is obsolete and will be removed in a future version. Use RunWithHostBuilderContextAsync(Action, Action, Action, Func>, IAspNetCoreHostFixture) instead.")] + public static Task RunWithHostBuilderContextAsync(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, Func> responseFactory = null) + { + return RunWithHostBuilderContextAsync(serviceSetup, pipelineSetup, hostSetup, responseFactory, null); + } + + /// + /// Runs a filter/middleware test. + /// + /// The which may be configured. + /// The which may be configured. + /// The which may be configured. + /// The function delegate that creates a from the . Default is a GET request to the root URL ("/"). + /// An optional implementation to use instead of the default instance. + /// A that represents the asynchronous operation. The task result contains the for the test server. + public static async Task RunWithHostBuilderContextAsync(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, Func> responseFactory = null, IAspNetCoreHostFixture hostFixture = null) { - using var client = CreateWithHostBuilderContext(serviceSetup, pipelineSetup, hostSetup).Host.GetTestClient(); + using var client = CreateWithHostBuilderContext(serviceSetup, pipelineSetup, hostSetup, hostFixture).Host.GetTestClient(); return await client.ToHttpResponseMessageAsync(responseFactory).ConfigureAwait(false); } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs index 9e9cda9..7bfbc46 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs @@ -4,33 +4,29 @@ namespace Codebelt.Extensions.Xunit.Hosting { - internal sealed class GenericHostTest : HostTest, IGenericHostTest + internal sealed class GenericHostTest : HostTest, IGenericHostTest { private readonly Action _serviceConfigurator; private readonly Action _serviceConfiguratorWithContext; private readonly Action _hostConfigurator; private HostBuilderContext _hostBuilderContext; - internal GenericHostTest(Action serviceConfigurator, Action hostConfigurator, HostFixture hostFixture) : base(hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + internal GenericHostTest(Action serviceConfigurator, Action hostConfigurator, IHostFixture hostFixture) : base(hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) { _serviceConfigurator = serviceConfigurator; _hostConfigurator = hostConfigurator; - if (!hostFixture.HasValidState()) - { - hostFixture.ConfigureHostCallback = ConfigureHost; - hostFixture.ConfigureCallback = Configure; - hostFixture.ConfigureServicesCallback = ConfigureServices; - hostFixture.ConfigureHost(this); - } - Host = hostFixture.Host; - ServiceProvider = hostFixture.Host.Services; - Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + InitializeHostFixture(hostFixture); } - internal GenericHostTest(Action serviceConfigurator, Action hostConfigurator, HostFixture hostFixture) : base(hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + internal GenericHostTest(Action serviceConfigurator, Action hostConfigurator, IHostFixture hostFixture) : base(hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) { _serviceConfiguratorWithContext = serviceConfigurator; _hostConfigurator = hostConfigurator; + InitializeHostFixture(hostFixture); + } + + private void InitializeHostFixture(IHostFixture hostFixture) + { if (!hostFixture.HasValidState()) { hostFixture.ConfigureHostCallback = ConfigureHost; @@ -43,11 +39,6 @@ internal GenericHostTest(Action serviceC Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); } - protected override void InitializeHostFixture(HostFixture hostFixture) - { - // intentionally left blank due to constructor initialization (need to refactor this "call from virtual method" challenge) - } - protected override void ConfigureHost(IHostBuilder hb) { _hostBuilderContext = new HostBuilderContext(hb.Properties); diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTestFactory.cs index 0c3fa72..1a694a9 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTestFactory.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTestFactory.cs @@ -15,9 +15,10 @@ public static class GenericHostTestFactory /// The which may be configured. /// The which may be configured. /// An instance of an implementation. + [Obsolete("This method is obsolete and will be removed in a future version. Use Create(Action, Action, IHostFixture) instead.")] public static IGenericHostTest Create(Action serviceSetup = null, Action hostSetup = null) { - return new GenericHostTest(serviceSetup, hostSetup, new HostFixture()); + return Create(serviceSetup, hostSetup, null); } /// @@ -25,10 +26,35 @@ public static IGenericHostTest Create(Action serviceSetup = /// /// The which may be configured. /// The which may be configured. + /// An optional implementation to use instead of the default instance. /// An instance of an implementation. + public static IGenericHostTest Create(Action serviceSetup = null, Action hostSetup = null, IHostFixture hostFixture = null) + { + return new GenericHostTest(serviceSetup, hostSetup, hostFixture ?? new HostFixture()); + } + + /// + /// Creates and returns an implementation. + /// + /// The which may be configured. + /// The which may be configured. + /// An instance of an implementation. + [Obsolete("This method is obsolete and will be removed in a future version. Use CreateWithHostBuilderContext(Action, Action, IHostFixture) instead.")] public static IGenericHostTest CreateWithHostBuilderContext(Action serviceSetup = null, Action hostSetup = null) { - return new GenericHostTest(serviceSetup, hostSetup, new HostFixture()); + return CreateWithHostBuilderContext(serviceSetup, hostSetup, null); + } + + /// + /// Creates and returns an implementation. + /// + /// The which may be configured. + /// The which may be configured. + /// An optional implementation to use instead of the default instance. + /// An instance of an implementation. + public static IGenericHostTest CreateWithHostBuilderContext(Action serviceSetup = null, Action hostSetup = null, IHostFixture hostFixture = null) + { + return new GenericHostTest(serviceSetup, hostSetup, hostFixture ?? new HostFixture()); } } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs b/src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs index 34a41d4..f899e98 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/GlobalSuppressions.cs @@ -7,3 +7,5 @@ [assembly: SuppressMessage("Major Code Smell", "S3881:\"IDisposable\" should be implemented correctly", Justification = "This is an implementation of the IDisposable interface tailored to avoid wrong implementations.", Scope = "type", Target = "~T:Codebelt.Extensions.Xunit.Hosting.HostFixture")] [assembly: SuppressMessage("Major Code Smell", "S3971:\"GC.SuppressFinalize\" should not be called", Justification = "False-Positive due to IAsyncDisposable living side-by-side with IDisposable.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Hosting.HostFixture.DisposeAsync~System.Threading.Tasks.ValueTask")] +[assembly: SuppressMessage("Blocker Code Smell", "S3427:Method overloads with default parameter values should not overlap", Justification = "Avoid bumping major version by providing an extra overloaded member as optional argument.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Hosting.GenericHostTestFactory.Create(System.Action{Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},Codebelt.Extensions.Xunit.Hosting.IHostFixture)~Codebelt.Extensions.Xunit.Hosting.IGenericHostTest")] +[assembly: SuppressMessage("Blocker Code Smell", "S3427:Method overloads with default parameter values should not overlap", Justification = "Avoid bumping major version by providing an extra overloaded member as optional argument.", Scope = "member", Target = "~M:Codebelt.Extensions.Xunit.Hosting.GenericHostTestFactory.CreateWithHostBuilderContext(System.Action{Microsoft.Extensions.Hosting.HostBuilderContext,Microsoft.Extensions.DependencyInjection.IServiceCollection},System.Action{Microsoft.Extensions.Hosting.IHostBuilder},Codebelt.Extensions.Xunit.Hosting.IHostFixture)~Codebelt.Extensions.Xunit.Hosting.IGenericHostTest")] From 6f38098d0ce6d4eb38c625728a5827a1eed1e931 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 30 Mar 2025 22:57:52 +0200 Subject: [PATCH 05/17] :building_construction: internal refactoring moving test-store to provider and GetTestStore improvements --- .../LoggerExtensions.cs | 23 +++++++++++++++++-- .../XunitTestLogger.cs | 15 ++++++++---- .../XunitTestLoggerProvider.cs | 11 ++++++--- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting/LoggerExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting/LoggerExtensions.cs index edfd5aa..a263944 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/LoggerExtensions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/LoggerExtensions.cs @@ -22,7 +22,7 @@ public static class LoggerExtensions /// /// does not contain a test store. /// - public static ITestStore GetTestStore(this ILogger logger) + public static ITestStore GetTestStore(this ILogger logger) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var loggerType = logger.GetType(); @@ -39,12 +39,31 @@ public static ITestStore GetTestStore(this ILogger l var providerType = loggerInformationType.GetProperty("ProviderType")?.GetValue(loggerInformation) as Type; if (providerType == typeof(XunitTestLoggerProvider)) { - return loggerInformationType.GetProperty("Logger")?.GetValue(loggerInformation) as InMemoryTestStore; + var xunitTestLogger = loggerInformationType.GetProperty("Logger")?.GetValue(loggerInformation) as XunitTestLogger; + if (xunitTestLogger == null) { continue; } + return xunitTestLogger.Provider; } } } } throw new ArgumentException($"Logger does not contain a test store; did you remember to call {nameof(ServiceCollectionExtensions.AddXunitTestLogging)} before calling this method?", nameof(logger)); } + + /// + /// Returns the associated that is provided when settings up services from . + /// + /// The from which to retrieve the . + /// Returns an implementation of with all logged entries expressed as . + /// + /// cannot be null. + /// + /// + /// does not contain a test store. + /// + [Obsolete($"This method will be removed in a future version. Please use non-generic equivalent {nameof(GetTestStore)}.")] + public static ITestStore GetTestStore(this ILogger logger) + { + return GetTestStore((ILogger)logger); + } } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLogger.cs b/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLogger.cs index e9a317d..0bd8df3 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLogger.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLogger.cs @@ -5,18 +5,21 @@ namespace Codebelt.Extensions.Xunit.Hosting { - internal sealed class XunitTestLogger : InMemoryTestStore, ILogger, IDisposable + internal sealed class XunitTestLogger : ILogger, IDisposable { private readonly ITestOutputHelperAccessor _accessor; private readonly ITestOutputHelper _output; + private readonly XunitTestLoggerProvider _provider; - public XunitTestLogger(ITestOutputHelper output) + public XunitTestLogger(XunitTestLoggerProvider provider, ITestOutputHelper output) { + _provider = provider; _output = output; } - public XunitTestLogger(ITestOutputHelperAccessor accessor) + public XunitTestLogger(XunitTestLoggerProvider provider, ITestOutputHelperAccessor accessor) { + _provider = provider; _accessor = accessor; } @@ -26,6 +29,9 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except if (exception != null) { builder.AppendLine().Append(exception).AppendLine(); } var message = builder.ToString(); + + _provider.WriteLoggerEntry(logLevel, eventId, message); + if (_accessor != null) { if (_accessor.TestOutput == null) { throw new InvalidOperationException($"{nameof(ITestOutputHelperAccessor)}.{nameof(ITestOutputHelperAccessor.TestOutput)} is null."); } @@ -35,9 +41,10 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except { _output.WriteLine(message); } - Add(new XunitTestLoggerEntry(logLevel, eventId, message)); } + public XunitTestLoggerProvider Provider => _provider; + public bool IsEnabled(LogLevel logLevel) { return logLevel != LogLevel.None; diff --git a/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs b/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs index 677d011..f01ef22 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/XunitTestLoggerProvider.cs @@ -4,7 +4,7 @@ namespace Codebelt.Extensions.Xunit.Hosting { - internal sealed class XunitTestLoggerProvider : ILoggerProvider + internal sealed class XunitTestLoggerProvider : InMemoryTestStore, ILoggerProvider { private readonly ConcurrentDictionary _loggers = new(); private readonly ITestOutputHelperAccessor _accessor; @@ -23,8 +23,13 @@ public XunitTestLoggerProvider(ITestOutputHelperAccessor accessor) public ILogger CreateLogger(string categoryName) { return _loggers.GetOrAdd(categoryName, _ => _accessor != null - ? new XunitTestLogger(_accessor) - : new XunitTestLogger(_output)); + ? new XunitTestLogger(this, _accessor) + : new XunitTestLogger(this, _output)); + } + + public void WriteLoggerEntry(LogLevel logLevel, EventId eventId, string message) + { + Add(new XunitTestLoggerEntry(logLevel, eventId, message)); } public void Dispose() From bc43d836662f8a1f479ff0d82e9e9f63c8847b88 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 30 Mar 2025 23:14:53 +0200 Subject: [PATCH 06/17] :art: updated cref's to be valid --- .../AspNetCoreHostFixtureExtensions.cs | 4 ++-- .../HttpClientExtensions.cs | 2 +- .../HostFixtureExtensions.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs index 38e1198..a704a9f 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs @@ -12,8 +12,8 @@ public static class AspNetCoreHostFixtureExtensions /// true if the specified has a valid state; otherwise, false. /// /// A valid state is defined as having non-null values for the following properties: - /// , , - /// , and . + /// , , + /// , and . /// public static bool HasValidState(this IAspNetCoreHostFixture fixture) { diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HttpClientExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HttpClientExtensions.cs index 272a4a5..93f4a08 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HttpClientExtensions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HttpClientExtensions.cs @@ -18,7 +18,7 @@ public static class HttpClientExtensions /// /// cannot be null. /// - /// Designed to be used in conjunction with and . + /// Designed to be used in conjunction with and . public static async Task ToHttpResponseMessageAsync(this HttpClient client, Func> responseFactory = null) { if (client == null) { throw new ArgumentNullException(nameof(client)); } diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs index 00873f0..3ed8e1f 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs @@ -12,8 +12,8 @@ public static class HostFixtureExtensions /// true if the specified has a valid state; otherwise, false. /// /// A valid state is defined as having non-null values for the following properties: - /// , , - /// and . + /// , , + /// and . /// public static bool HasValidState(this IHostFixture fixture) { From 01a12a98d6ae821266410cba51ce80f7268215ed Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 30 Mar 2025 23:16:05 +0200 Subject: [PATCH 07/17] :white_check_mark: added new / updated existing test --- .../AspNetCoreHostTestTest.cs | 13 + .../Assets/ValidHostTest.cs | 12 + .../MvcAspNetCoreHostTestTest.cs | 13 + .../WebHostTestFactoryTest.cs | 291 +++++++++++------- .../Assets/ValidHostTest.cs | 11 + .../HostTestTest.cs | 12 + 6 files changed, 238 insertions(+), 114 deletions(-) diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs index 07fb254..222a13c 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs @@ -21,6 +21,19 @@ public class AspNetCoreHostTestTest : AspNetCoreHostTest public AspNetCoreHostTestTest(AspNetCoreHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureApplicationCallback = ConfigureApplication; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + ServiceProvider = hostFixture.Host.Services; + Application = hostFixture.Application; + Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + _pipeline = hostFixture.Application; _provider = hostFixture.ServiceProvider; _provider.GetRequiredService().TestOutput = output; diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs index 544cc78..e0848a9 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs @@ -7,6 +7,18 @@ public class ValidHostTest : AspNetCoreHostTest { public ValidHostTest(AspNetCoreHostFixture hostFixture) : base(hostFixture) { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureApplicationCallback = ConfigureApplication; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + ServiceProvider = hostFixture.Host.Services; + Application = hostFixture.Application; + Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); } public override void ConfigureServices(IServiceCollection services) diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcAspNetCoreHostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcAspNetCoreHostTestTest.cs index bbaeb66..4a5bbae 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcAspNetCoreHostTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcAspNetCoreHostTestTest.cs @@ -17,6 +17,19 @@ public class MvcAspNetCoreHostTestTest : AspNetCoreHostTest().TestOutput = output; _hostFixture = hostFixture; _client = hostFixture.Host.GetTestClient(); diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestFactoryTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestFactoryTest.cs index 2ebc78a..99ac85f 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestFactoryTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestFactoryTest.cs @@ -1,114 +1,177 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Cuemon.AspNetCore.Diagnostics; -using Cuemon.Extensions.AspNetCore.Diagnostics; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Xunit; -using Xunit.Abstractions; - -namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore -{ - public class WebHostTestFactoryTest : Test - { - public WebHostTestFactoryTest(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public void Create_CallerTypeShouldHaveDeclaringTypeOfMiddlewareTestFactoryTest() - { - Type sut1 = GetType(); - string sut2 = null; - var middleware = WebHostTestFactory.Create(Assert.NotNull, Assert.NotNull, host => - { - host.ConfigureAppConfiguration((context, _) => - { - sut2 = context.HostingEnvironment.ApplicationName; - }); - }); - - Assert.True(sut1 == middleware.CallerType.DeclaringType); - Assert.Equal(GetType().Assembly.GetName().Name, sut2); - } - - [Fact] - public Task RunAsync_ShouldHaveApplicationNameEqualToThisAssembly() - { - return WebHostTestFactory.RunAsync(Assert.NotNull, Assert.NotNull, host => - { - host.ConfigureAppConfiguration((context, _) => - { - TestOutput.WriteLine(context.HostingEnvironment.ApplicationName); - Assert.Equal(GetType().Assembly.GetName().Name, context.HostingEnvironment.ApplicationName); - }); - }); - } - - [Fact] - public Task RunWithHostBuilderContextAsync_ShouldHaveApplicationNameEqualToThisAssembly_WithHostBuilderContext() - { - return WebHostTestFactory.RunWithHostBuilderContextAsync((context, app) => - { - Assert.NotNull(context); - Assert.NotNull(context.HostingEnvironment); - Assert.NotNull(context.Configuration); - Assert.NotNull(context.Properties); - Assert.NotNull(app); - }, - (context, services) => - { - Assert.NotNull(context); - Assert.NotNull(context.HostingEnvironment); - Assert.NotNull(context.Configuration); - Assert.NotNull(context.Properties); - Assert.NotNull(services); - }, - host => - { - host.ConfigureAppConfiguration((context, configuration) => - { - TestOutput.WriteLine(context.HostingEnvironment.ApplicationName); - Assert.Equal(GetType().Assembly.GetName().Name, context.HostingEnvironment.ApplicationName); - }); - }); - } - - [Fact] - public async Task RunAsync_ShouldWorkWithXunitTestLogging() - { - using var response = await WebHostTestFactory.RunAsync( - services => - { - services.AddXunitTestLogging(TestOutput); - services.AddServerTiming(o => o.SuppressHeaderPredicate = _ => false); - } - , app => - { - app.UseServerTiming(); - app.Use(async (context, next) => - { - var sw = Stopwatch.StartNew(); - context.Response.OnStarting(() => - { - sw.Stop(); - context.RequestServices.GetRequiredService().AddServerTiming("use-middleware", sw.Elapsed); - return Task.CompletedTask; - }); - await next(context).ConfigureAwait(false); - }); - app.Run(context => - { - Thread.Sleep(400); - return context.Response.WriteAsync("Hello World!"); - }); - }).ConfigureAwait(false); - - Assert.StartsWith("use-middleware;dur=", response.Headers.Single(kvp => kvp.Key == ServerTiming.HeaderName).Value.FirstOrDefault()); - } - } -} +using System; +using System.Diagnostics; +using System.Linq; +using System.Security; +using System.Threading; +using System.Threading.Tasks; +using Cuemon.AspNetCore.Diagnostics; +using Cuemon.Extensions.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + public class WebHostTestFactoryTest : Test + { + public WebHostTestFactoryTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Create_ShouldThrowSecurityException_DueToBlockingAspNetCoreHostFixture() + { + Assert.Throws(() => WebHostTestFactory.Create( + services => + { + services.AddXunitTestLogging(TestOutput); + services.AddAuthorization(o => + { + o.AddPolicy("Test", _ => throw new SecurityException()); + }); + }, + app => + { + var policy = app.ApplicationServices.GetRequiredService(); + }, + host => + { + host.UseDefaultServiceProvider(o => + { + o.ValidateOnBuild = false; + o.ValidateScopes = false; + }); + }, + new BlockingAspNetCoreHostFixture())); + } + + [Fact] + public void Create_ShouldCaptureSecurityException_DueToNonBlockingAspNetCoreHostFixture() + { + using (var startup = WebHostTestFactory.Create( + services => + { + services.AddXunitTestLogging(TestOutput); + + services.AddAuthorization(o => { o.AddPolicy("Test", _ => throw new SecurityException()); }); + }, + app => + { + var policy = app.ApplicationServices.GetRequiredService(); + }, + host => + { + host.UseDefaultServiceProvider(o => + { + o.ValidateOnBuild = false; + o.ValidateScopes = false; + }); + })) + { + var loggerStore = startup.ServiceProvider.GetRequiredService>().GetTestStore(); + var message = loggerStore.Query(entry => entry.Severity == LogLevel.Critical && entry.Message.Contains("SecurityException", StringComparison.OrdinalIgnoreCase)).SingleOrDefault()?.Message; + Assert.NotNull(message); + Assert.Contains("System.Security.SecurityException: Security error.", message); + } + } + + [Fact] + public void Create_CallerTypeShouldHaveDeclaringTypeOfMiddlewareTestFactoryTest() + { + Type sut1 = GetType(); + string sut2 = null; + var middleware = WebHostTestFactory.Create(Assert.NotNull, Assert.NotNull, host => + { + host.ConfigureAppConfiguration((context, _) => + { + sut2 = context.HostingEnvironment.ApplicationName; + }); + }); + + Assert.True(sut1 == middleware.CallerType.DeclaringType); + Assert.Equal(GetType().Assembly.GetName().Name, sut2); + } + + [Fact] + public Task RunAsync_ShouldHaveApplicationNameEqualToThisAssembly() + { + return WebHostTestFactory.RunAsync(Assert.NotNull, Assert.NotNull, host => + { + host.ConfigureAppConfiguration((context, _) => + { + TestOutput.WriteLine(context.HostingEnvironment.ApplicationName); + Assert.Equal(GetType().Assembly.GetName().Name, context.HostingEnvironment.ApplicationName); + }); + }, hostFixture: null); + } + + [Fact] + public Task RunWithHostBuilderContextAsync_ShouldHaveApplicationNameEqualToThisAssembly_WithHostBuilderContext() + { + return WebHostTestFactory.RunWithHostBuilderContextAsync((context, app) => + { + Assert.NotNull(context); + Assert.NotNull(context.HostingEnvironment); + Assert.NotNull(context.Configuration); + Assert.NotNull(context.Properties); + Assert.NotNull(app); + }, + (context, services) => + { + Assert.NotNull(context); + Assert.NotNull(context.HostingEnvironment); + Assert.NotNull(context.Configuration); + Assert.NotNull(context.Properties); + Assert.NotNull(services); + }, + host => + { + host.ConfigureAppConfiguration((context, configuration) => + { + TestOutput.WriteLine(context.HostingEnvironment.ApplicationName); + Assert.Equal(GetType().Assembly.GetName().Name, context.HostingEnvironment.ApplicationName); + }); + }, + hostFixture: null); + } + + [Fact] + public async Task RunAsync_ShouldWorkWithXunitTestLogging() + { + using var response = await WebHostTestFactory.RunAsync( + services => + { + services.AddXunitTestLogging(TestOutput); + services.AddServerTiming(o => o.SuppressHeaderPredicate = _ => false); + }, + app => + { + app.UseServerTiming(); + app.Use(async (context, next) => + { + var sw = Stopwatch.StartNew(); + context.Response.OnStarting(() => + { + sw.Stop(); + context.RequestServices.GetRequiredService().AddServerTiming("use-middleware", sw.Elapsed); + return Task.CompletedTask; + }); + await next(context).ConfigureAwait(false); + }); + app.Run(context => + { + Thread.Sleep(400); + return context.Response.WriteAsync("Hello World!"); + }); + }, + hostFixture: null).ConfigureAwait(false); + + Assert.StartsWith("use-middleware;dur=", response.Headers.Single(kvp => kvp.Key == ServerTiming.HeaderName).Value.FirstOrDefault()); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs index 4a5d268..abbea82 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs @@ -1,6 +1,7 @@ using System; using Microsoft.Extensions.DependencyInjection; using Xunit.Abstractions; +using static System.Net.Mime.MediaTypeNames; namespace Codebelt.Extensions.Xunit.Hosting.Assets { @@ -8,6 +9,16 @@ public class ValidHostTest : HostTest { public ValidHostTest(HostFixture hostFixture) : base(hostFixture) { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + ServiceProvider = hostFixture.Host.Services; + Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); } public override void ConfigureServices(IServiceCollection services) diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs index 5f89a32..08a67a8 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs @@ -20,8 +20,20 @@ public class HostTestTest : HostTest public HostTestTest(HostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + ServiceProvider = hostFixture.Host.Services; + Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + _scope = hostFixture.ServiceProvider.CreateScope(); _correlationsFactory = () => _scope.ServiceProvider.GetServices().ToList(); + } [Fact, Priority(1)] From 945108da48550a994478fa9d94605822a5ede651 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Sun, 30 Mar 2025 23:16:17 +0200 Subject: [PATCH 08/17] :memo: updated README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f1578d2..97e9316 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ An open-source project (MIT license) that targets and complements the [xUnit.net](https://xunit.net/) test platform. It provides a uniform and convenient way of doing unit test for all project types in .NET. +Full documentation (generated by [DocFx](https://github.com/dotnet/docfx)) located here: https://xunit.codebelt.net/ + ### 📦 Standalone Packages Provides a focused API for unit-testing specific types of .NET projects. From 1c6f6254720b8e1a4a4fda0ee695006a57c8875d Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 18:05:00 +0200 Subject: [PATCH 09/17] :construction_worker: initial move to reusable workflows --- .github/workflows/pipelines.yml | 88 ++++++--------------------------- 1 file changed, 14 insertions(+), 74 deletions(-) diff --git a/.github/workflows/pipelines.yml b/.github/workflows/pipelines.yml index 5a7f2ff..ae33294 100644 --- a/.github/workflows/pipelines.yml +++ b/.github/workflows/pipelines.yml @@ -125,87 +125,27 @@ jobs: buildSwitches: -p:SkipSignAssembly=true sonarcloud: - name: 🔬 Code Quality Analysis + name: call-sonarcloud needs: [build,test] - runs-on: ubuntu-22.04 - timeout-minutes: 15 - steps: - - name: Checkout - uses: codebeltnet/git-checkout@v1 - - - name: Install .NET - uses: codebeltnet/install-dotnet@v1 - with: - includePreview: true - - - name: Install .NET Tool - Sonar Scanner - uses: codebeltnet/dotnet-tool-install-sonarscanner@v1 - - - name: Restore Dependencies - uses: codebeltnet/dotnet-restore@v2 - - - name: Run SonarCloud Analysis - uses: codebeltnet/sonarcloud-scan@v1 - with: - token: ${{ secrets.SONAR_TOKEN }} - organization: geekle - projectKey: xunit - version: ${{ needs.build.outputs.version }} - - - name: Build - uses: codebeltnet/dotnet-build@v2 - with: - buildSwitches: -p:SkipSignAssembly=true - uploadBuildArtifact: false - - - name: Finalize SonarCloud Analysis - uses: codebeltnet/sonarcloud-scan-finalize@v1 - with: - token: ${{ secrets.SONAR_TOKEN }} + uses: codebeltnet/jobs-sonarcloud/.github/workflows/default.yml@v1 + with: + organization: geekle + projectKey: xunit + version: ${{ needs.build.outputs.version }} + secrets: inherit codecov: - name: 📊 Code Coverage Analysis + name: call-codecov needs: [build,test] - runs-on: ubuntu-22.04 - timeout-minutes: 15 - steps: - - name: Checkout - uses: codebeltnet/git-checkout@v1 + uses: codebeltnet/jobs-codecov/.github/workflows/default.yml@v1 + with: + repository: codebeltnet/xunit + secrets: inherit - - name: Run CodeCov Analysis - uses: codebeltnet/codecov-scan@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - repository: codebeltnet/xunit - codeql: - name: 🛡️ Security Analysis + name: call-codeql needs: [build,test] - runs-on: ubuntu-22.04 - timeout-minutes: 15 - steps: - - name: Checkout - uses: codebeltnet/git-checkout@v1 - - - name: Install .NET - uses: codebeltnet/install-dotnet@v1 - with: - includePreview: true - - - name: Restore Dependencies - uses: codebeltnet/dotnet-restore@v2 - - - name: Prepare CodeQL SAST Analysis - uses: codebeltnet/codeql-scan@v1 - - - name: Build - uses: codebeltnet/dotnet-build@v2 - with: - buildSwitches: -p:SkipSignAssembly=true - uploadBuildArtifact: false - - - name: Finalize CodeQL SAST Analysis - uses: codebeltnet/codeql-scan-finalize@v1 + uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1 deploy: if: github.event_name != 'pull_request' From 42679b2abebdcca74dc78426e26e88e8d5b72a06 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 18:07:29 +0200 Subject: [PATCH 10/17] :heavy_minus_sign: remove dependency to Microsoft.SourceLink.GitHub (part of SDK) --- Directory.Build.props | 1 - 1 file changed, 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index cd4aba9..01fbc53 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -42,7 +42,6 @@ - From 73ebd41d82b69e200a25c49fb9594ef6197ccf57 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 18:07:52 +0200 Subject: [PATCH 11/17] :arrow_up: bump dependencies --- Directory.Packages.props | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 052a79e..4a1813d 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,15 +4,14 @@ true - - - + + + - - + - + @@ -21,16 +20,16 @@ - + - - - - - - + + + + + + From f9b8e0535bf65ca78ce0c61f47d2c74cf0d5e6d7 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 19:35:03 +0200 Subject: [PATCH 12/17] :arrow_up: bump dependencies --- Directory.Packages.props | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4a1813d..5c09999 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,10 +4,10 @@ true - - - - + + + + From e992e954ee71f127f9e93f159c6a2656999b682b Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 19:35:03 +0200 Subject: [PATCH 13/17] :arrow_up: bump dependencies --- Directory.Packages.props | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4a1813d..aae2599 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,10 +4,10 @@ true - - - - + + + + @@ -37,6 +37,6 @@ - + \ No newline at end of file From 7739cb42e074ed888e9a14ce1f7ac459053840ec Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 20:56:12 +0200 Subject: [PATCH 14/17] :rewind: retain binary compatibility from HostTest --- .../WebHostTest.cs | 2 +- src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs | 2 +- src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs index b1c9f8d..66154f3 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs @@ -30,7 +30,7 @@ internal WebHostTest(Action serviceConfi InitializeHostFixture(hostFixture); } - private void InitializeHostFixture(IAspNetCoreHostFixture hostFixture) + private new void InitializeHostFixture(IAspNetCoreHostFixture hostFixture) { if (!hostFixture.HasValidState()) { diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs index 7bfbc46..db25f7d 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs @@ -25,7 +25,7 @@ internal GenericHostTest(Action serviceC InitializeHostFixture(hostFixture); } - private void InitializeHostFixture(IHostFixture hostFixture) + private new void InitializeHostFixture(IHostFixture hostFixture) { if (!hostFixture.HasValidState()) { diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs index 7fa52a1..1dfb236 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs @@ -27,6 +27,15 @@ protected HostTest(T hostFixture, ITestOutputHelper output = null, Type callerTy if (hostFixture == null) { throw new ArgumentNullException(nameof(hostFixture)); } } + /// + /// Initializes the specified host fixture. + /// + /// The host fixture to initialize. + [Obsolete("This method is obsolete and will be removed in a future version. It remains only to support binary compatibility.")] + protected virtual void InitializeHostFixture(T hostFixture) + { + } + /// /// Gets the initialized by the . /// From 7f147e2b776af612d5ea8f43808cbeb1fef64333 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 21:09:39 +0200 Subject: [PATCH 15/17] :package: updated NuGet package definition --- .../PackageReleaseNotes.txt | 14 +++++++++++++- .../PackageReleaseNotes.txt | 15 ++++++++++++++- .../PackageReleaseNotes.txt | 15 ++++++++++++++- .../PackageReleaseNotes.txt | 8 +++++++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt index e486582..8d9c9cf 100644 --- a/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt @@ -1,4 +1,16 @@ -Version 9.0.0 +Version 9.1.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +Version 9.0.1 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +Version 9.0.0 Availability: .NET 9 and .NET 8   # ALM diff --git a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt index bf74cd3..df4c078 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt @@ -1,4 +1,17 @@ -Version 9.0.1 +Version 9.1.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +# New Features +- ADDED AspNetCoreHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that consist of one extension method for the IAspNetCoreHostFixture interface: HasValidState +- ADDED BlockingAspNetCoreHostFixture class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that provides a blocking implementation of the AspNetCoreHostFixture implementation +  +# Improvements +- CHANGED WebHostTestFactory class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace to accept an optional argument taking a custom implementation of IAspNetCoreHostFixture (promote DIP) +  +Version 9.0.1 Availability: .NET 9 and .NET 8   # ALM diff --git a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt index 4859830..e1d2f13 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt @@ -1,4 +1,17 @@ -Version 9.0.1 +Version 9.1.0 +Availability: .NET 9, .NET 8 and .NET Standard 2.0 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +# New Features +- ADDED HostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace that consist of one extension method for the IHostFixture interface: HasValidState +  +# Improvements +- CHANGED HostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace to have an additional virtual method: StartConfiguredHost, which is called from the ConfigureHost method, to allow for custom implementations of the host startup process +- CHANGED GenericHostTestFactory class in the Codebelt.Extensions.Xunit.Hosting namespace to accept an optional argument taking a custom implementation of IHostFixture (promote DIP) +  +Version 9.0.1 Availability: .NET 9, .NET 8 and .NET Standard 2.0   # ALM diff --git a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt index 573f821..a302b61 100644 --- a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version 9.0.1 +Version 9.1.0 +Availability: .NET 9, .NET 8 and .NET Standard 2.0 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +Version 9.0.1 Availability: .NET 9, .NET 8 and .NET Standard 2.0   # ALM From 9795cff9e24bb0de9406cd8b87ec5fc0a566af82 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 21:12:06 +0200 Subject: [PATCH 16/17] :speech_balloon: updated community health pages --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20cd762..c93d7a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,25 @@ For more details, please refer to `PackageReleaseNotes.txt` on a per assembly ba > [!NOTE] > Changelog entries prior to version 8.4.0 was migrated from previous versions of Cuemon.Extensions.Xunit, Cuemon.Extensions.Xunit.Hosting, and Cuemon.Extensions.Xunit.Hosting.AspNetCore. +## [9.1.0] - 2025-03-31 + +This is a service update that primarily focuses on package dependencies including DIP improvements and a new blocking implementation of the AspNetCoreHostFixture. + +> [!WARNING] +> Although this release is backward compatible, do expect some design-time incompatibility due to changes in `GenericHostTestFactory` and `WebHostTestFactory`. + +### Added + +- HostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace that consist of one extension method for the IHostFixture interface: HasValidState +- AspNetCoreHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that consist of one extension method for the IAspNetCoreHostFixture interface: HasValidState +- BlockingAspNetCoreHostFixture class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that provides a blocking implementation of the AspNetCoreHostFixture implementation + +### Changed + +- HostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace to have an additional virtual method: StartConfiguredHost, which is called from the ConfigureHost method, to allow for custom implementations of the host startup process +- GenericHostTestFactory class in the Codebelt.Extensions.Xunit.Hosting namespace to accept an optional argument taking a custom implementation of IHostFixture (promote DIP) +- WebHostTestFactory class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace to accept an optional argument taking a custom implementation of IAspNetCoreHostFixture (promote DIP) + ## [9.0.1] - 2025-01-25 This is a service update that primarily focuses on package dependencies and minor improvements. From 854c2a8cf6b452681a4decc192a6c22e0b7fd418 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 31 Mar 2025 21:24:32 +0200 Subject: [PATCH 17/17] :arrow_up: bump dependency --- testenvironments.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testenvironments.json b/testenvironments.json index e296d68..1e4620e 100644 --- a/testenvironments.json +++ b/testenvironments.json @@ -9,7 +9,7 @@ { "name": "Docker-Ubuntu", "type": "docker", - "dockerImage": "gimlichael/ubuntu-testrunner:net8.0.405-9.0.102" + "dockerImage": "gimlichael/ubuntu-testrunner:net8.0.407-9.0.202" } ] }