From 7c866957b8c1235b32def611620dcac8fe92b3dd Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 16:50:36 +0200 Subject: [PATCH 01/15] :truck: rename to IEnvironmentTest --- .../{IHostingEnvironmentTest.cs => IEnvironmentTest.cs} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/Codebelt.Extensions.Xunit.Hosting/{IHostingEnvironmentTest.cs => IEnvironmentTest.cs} (75%) diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IHostingEnvironmentTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/IEnvironmentTest.cs similarity index 75% rename from src/Codebelt.Extensions.Xunit.Hosting/IHostingEnvironmentTest.cs rename to src/Codebelt.Extensions.Xunit.Hosting/IEnvironmentTest.cs index 6985c8e..95c3a22 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostingEnvironmentTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IEnvironmentTest.cs @@ -3,14 +3,14 @@ namespace Codebelt.Extensions.Xunit.Hosting { /// - /// Represents the members needed for DI testing with support for HostingEnvironment. + /// Represents the members needed for DI testing with support for . /// - public interface IHostingEnvironmentTest + public interface IEnvironmentTest { /// /// Gets the initialized by the . /// /// The initialized by the . - IHostEnvironment HostingEnvironment { get; } + IHostEnvironment Environment { get; } } } From 3de046c12816f047ae5f9db829e699fad7efa14b Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 16:56:38 +0200 Subject: [PATCH 02/15] :truck: renamed to HostTest and HostTestFactory --- .../GenericHostTestFactory.cs | 60 ------------------- .../HostTestFactory.cs | 36 +++++++++++ .../HostTest.cs} | 15 +++-- 3 files changed, 43 insertions(+), 68 deletions(-) delete mode 100644 src/Codebelt.Extensions.Xunit.Hosting/GenericHostTestFactory.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/HostTestFactory.cs rename src/Codebelt.Extensions.Xunit.Hosting/{GenericHostTest.cs => Internal/HostTest.cs} (71%) diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTestFactory.cs deleted file mode 100644 index 1a694a9..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTestFactory.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; - -namespace Codebelt.Extensions.Xunit.Hosting -{ - /// - /// Provides a set of static methods for unit testing. - /// - public static class GenericHostTestFactory - { - /// - /// 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 Create(Action, Action, IHostFixture) instead.")] - public static IGenericHostTest Create(Action serviceSetup = null, Action hostSetup = null) - { - return Create(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 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 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/HostTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostTestFactory.cs new file mode 100644 index 0000000..d02c20c --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostTestFactory.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Provides a set of static methods for unit testing. + /// + public static class HostTestFactory + { + /// + /// 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 IHostTest Create(Action serviceSetup = null, Action hostSetup = null, IGenericHostFixture hostFixture = null) + { + return new Internal.HostTest(serviceSetup, hostSetup, hostFixture ?? new GenericHostFixture()); + } + + /// + /// 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 IHostTest CreateWithHostBuilderContext(Action serviceSetup = null, Action hostSetup = null, IGenericHostFixture hostFixture = null) + { + return new Internal.HostTest(serviceSetup, hostSetup, hostFixture ?? new GenericHostFixture()); + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/Internal/HostTest.cs similarity index 71% rename from src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs rename to src/Codebelt.Extensions.Xunit.Hosting/Internal/HostTest.cs index cfae46d..6857372 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/Internal/HostTest.cs @@ -2,30 +2,30 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace Codebelt.Extensions.Xunit.Hosting +namespace Codebelt.Extensions.Xunit.Hosting.Internal { - internal sealed class GenericHostTest : HostTest + internal sealed class HostTest : HostTest { private readonly Action _serviceConfigurator; private readonly Action _serviceConfiguratorWithContext; private readonly Action _hostConfigurator; private HostBuilderContext _hostBuilderContext; - internal GenericHostTest(Action serviceConfigurator, Action hostConfigurator, IHostFixture hostFixture) : base(true, hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + internal HostTest(Action serviceConfigurator, Action hostConfigurator, IGenericHostFixture hostFixture) : base(true, hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) { _serviceConfigurator = serviceConfigurator; _hostConfigurator = hostConfigurator; InitializeHostFixture(hostFixture); } - internal GenericHostTest(Action serviceConfigurator, Action hostConfigurator, IHostFixture hostFixture) : base(true, hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + internal HostTest(Action serviceConfigurator, Action hostConfigurator, IGenericHostFixture hostFixture) : base(true, hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) { _serviceConfiguratorWithContext = serviceConfigurator; _hostConfigurator = hostConfigurator; InitializeHostFixture(hostFixture); } - private new void InitializeHostFixture(IHostFixture hostFixture) + private void InitializeHostFixture(IGenericHostFixture hostFixture) { if (!hostFixture.HasValidState()) { @@ -35,8 +35,7 @@ internal GenericHostTest(Action serviceC hostFixture.ConfigureHost(this); } Host = hostFixture.Host; - ServiceProvider = hostFixture.Host.Services; - Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + Configure(hostFixture.Configuration, hostFixture.Environment); } protected override void ConfigureHost(IHostBuilder hb) @@ -51,7 +50,7 @@ public override void ConfigureServices(IServiceCollection services) _serviceConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc => { hbc.Configuration = Configuration; - hbc.HostingEnvironment = HostingEnvironment; + hbc.HostingEnvironment = Environment; return hbc; }), services); } From c400c7571edca3fe03ece11804457149d5062d56 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 16:59:03 +0200 Subject: [PATCH 03/15] :boom: removed due to redudant property on IHost (Services) --- .../IServiceTest.cs | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 src/Codebelt.Extensions.Xunit.Hosting/IServiceTest.cs diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IServiceTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/IServiceTest.cs deleted file mode 100644 index e293c23..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting/IServiceTest.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using Microsoft.Extensions.Hosting; - -namespace Codebelt.Extensions.Xunit.Hosting -{ - /// - /// Represents the members needed for DI services testing. - /// - public interface IServiceTest - { - /// - /// Gets the initialized by the . - /// - /// The initialized by the . - IServiceProvider ServiceProvider { get; } - } -} From def4c428587075e440165c1be74897644269af07 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 17:00:37 +0200 Subject: [PATCH 04/15] :boom: IGenericHostTest is now IHostTest --- .../IGenericHostTest.cs | 16 ---------------- .../IHostTest.cs | 11 +++++++---- 2 files changed, 7 insertions(+), 20 deletions(-) delete mode 100644 src/Codebelt.Extensions.Xunit.Hosting/IGenericHostTest.cs diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IGenericHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/IGenericHostTest.cs deleted file mode 100644 index d833639..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting/IGenericHostTest.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.Extensions.Hosting; - -namespace Codebelt.Extensions.Xunit.Hosting -{ - /// - /// Represents the members needed for bare-bone DI testing with support for . - /// - /// - /// - /// - /// - /// - public interface IGenericHostTest : IServiceTest, IConfigurationTest, IHostingEnvironmentTest, ITest, IHostTest - { - } -} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/IHostTest.cs index 3831bc2..0314a75 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IHostTest.cs @@ -3,14 +3,17 @@ namespace Codebelt.Extensions.Xunit.Hosting { /// - /// Represents the members needed for DI host testing. + /// Represents the members needed for bare-bone DI testing with support for . /// - public interface IHostTest + /// + /// + /// + public interface IHostTest : IConfigurationTest, IEnvironmentTest, ITest { /// - /// Gets the initialized by the . + /// Gets the initialized by the . /// - /// The initialized by the . + /// The initialized by the . IHost Host { get; } } } From 50666d880ccc0a94896570092b00c2ef28c853be Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 17:17:13 +0200 Subject: [PATCH 05/15] :building_construction: refactored to more confined responsibilities --- .../GenericHostFixture.cs | 93 +++++++++++++ .../GenericHostFixtureExtensions.cs | 24 ++++ .../HostFixture.cs | 127 ++++-------------- .../HostFixtureExtensions.cs | 26 ---- .../HostTest.cs | 113 ++++++++-------- .../IGenericHostFixture.cs | 32 +++++ .../IHostFixture.cs | 32 ++--- 7 files changed, 235 insertions(+), 212 deletions(-) create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/GenericHostFixture.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/GenericHostFixtureExtensions.cs delete mode 100644 src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/IGenericHostFixture.cs diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostFixture.cs new file mode 100644 index 0000000..2fe2d1d --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostFixture.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Provides a default implementation of the interface. + /// + /// + /// + public class GenericHostFixture : HostFixture, IGenericHostFixture + { + /// + /// Initializes a new instance of the class. + /// + public GenericHostFixture() + { + } + + /// + /// Creates and configures the of this instance. + /// + /// The object that inherits from . + /// was added to support those cases where the caller is required in the host configuration. + /// + /// is null. + /// + /// + /// is not assignable from . + /// + public virtual void ConfigureHost(Test hostTest) + { + if (hostTest == null) { throw new ArgumentNullException(nameof(hostTest)); } + if (!HasTypes(hostTest.GetType(), typeof(HostTest<>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(HostTest<>), $"{nameof(hostTest)} is not assignable from HostTest."); } + + var hb = new HostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseEnvironment("Development") + .ConfigureAppConfiguration((context, config) => + { + config + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", true, true) + .AddEnvironmentVariables(); + + ConfigureCallback(config.Build(), context.HostingEnvironment); + }) + .ConfigureServices((context, services) => + { + Configuration = context.Configuration; + Environment = context.HostingEnvironment; + ConfigureServicesCallback(services); + }) + .ConfigureHostConfiguration(builder => + { + builder.AddInMemoryCollection(new Dictionary + { + { HostDefaults.ApplicationKey, hostTest.CallerType.Assembly.GetName().Name } + }); + }); + +#if NET9_0_OR_GREATER + hb.UseDefaultServiceProvider(o => + { + o.ValidateOnBuild = true; + o.ValidateScopes = true; + }); +#endif + + ConfigureHostCallback(hb); + + Host = hb.Build(); + + HostRunnerCallback(Host); + } + + /// + /// Gets or sets the delegate that initializes the host builder. + /// + /// The delegate that initializes the host builder. + public Action ConfigureHostCallback { get; set; } + + /// + /// Gets or sets the delegate that adds services to the container. + /// + /// The delegate that adds services to the container. + public Action ConfigureServicesCallback { get; set; } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/GenericHostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostFixtureExtensions.cs new file mode 100644 index 0000000..0a11ffb --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/GenericHostFixtureExtensions.cs @@ -0,0 +1,24 @@ +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Extension methods for the interface. + /// + public static class GenericHostFixtureExtensions + { + /// + /// 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 IGenericHostFixture hostFixture) + { + return hostFixture.ConfigureServicesCallback != null && + hostFixture.Host != null && + hostFixture.ConfigureHostCallback != null; + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs index 2f24dac..a87138a 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs @@ -1,96 +1,31 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Xunit; namespace Codebelt.Extensions.Xunit.Hosting { /// - /// Provides a default implementation of the interface. + /// Represents the base class from which all implementations of xUnit fixture concept should derive. /// /// - public class HostFixture : IHostFixture, IAsyncLifetime + public abstract class HostFixture : IHostFixture, IAsyncLifetime { private readonly object _lock = new(); - - /// - /// Initializes a new instance of the class. - /// - public HostFixture() + private Action _hostRunnerCallback = host => { - } - - /// - /// Creates and configures the of this instance. - /// - /// The object that inherits from . - /// was added to support those cases where the caller is required in the host configuration. - /// - /// is null. - /// - /// - /// is not assignable from . - /// - public virtual void ConfigureHost(Test hostTest) - { - if (hostTest == null) { throw new ArgumentNullException(nameof(hostTest)); } - if (!HasTypes(hostTest.GetType(), typeof(HostTest<>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(HostTest<>), $"{nameof(hostTest)} is not assignable from HostTest."); } - - var hb = new HostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseEnvironment("Development") - .ConfigureAppConfiguration((context, config) => - { - config - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", true, true) - .AddEnvironmentVariables(); - - ConfigureCallback(config.Build(), context.HostingEnvironment); - }) - .ConfigureServices((context, services) => - { - Configuration = context.Configuration; - HostingEnvironment = context.HostingEnvironment; - ConfigureServicesCallback(services); - }) - .ConfigureHostConfiguration(builder => - { - builder.AddInMemoryCollection(new Dictionary - { - { HostDefaults.ApplicationKey, hostTest.CallerType.Assembly.GetName().Name } - }); - }); - -#if NET9_0_OR_GREATER - hb.UseDefaultServiceProvider(o => - { - o.ValidateOnBuild = true; - o.ValidateScopes = true; - }); -#endif - - ConfigureHostCallback(hb); - - Host = hb.Build(); - - 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(); + }; /// - /// Starts the by initialized . + /// Initializes a new instance of the class. /// - /// is responsible for configuring and setting the property. - protected virtual void StartConfiguredHost() + protected HostFixture() { - 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(); } /// @@ -114,6 +49,19 @@ protected static bool HasTypes(Type type, params Type[] types) return false; } + /// + /// Gets or sets the delegate responsible for running the . + /// + /// The delegate responsible for running the . + /// + /// cannot be null. + /// + protected Action HostRunnerCallback + { + get => _hostRunnerCallback; + set => _hostRunnerCallback = value ?? throw new ArgumentNullException(nameof(value), "The host runner delegate cannot be null."); + } + /// /// Gets or sets the delegate that initializes the test class. /// @@ -121,30 +69,12 @@ protected static bool HasTypes(Type type, params Type[] types) /// Mimics the Startup convention. public Action ConfigureCallback { get; set; } - /// - /// Gets or sets the delegate that initializes the host builder. - /// - /// The delegate that initializes the host builder. - public Action ConfigureHostCallback { get; set; } - - /// - /// Gets or sets the delegate that adds services to the container. - /// - /// The delegate that adds services to the container. - public Action ConfigureServicesCallback { get; set; } - /// /// Gets or sets the initialized by this instance. /// /// The initialized by this instance. public IHost Host { get; protected set; } - /// - /// Gets the initialized by this instance. - /// - /// The initialized by this instance. - public IServiceProvider ServiceProvider => Host?.Services; - /// /// Gets the initialized by this instance. /// @@ -155,7 +85,7 @@ protected static bool HasTypes(Type type, params Type[] types) /// Gets the initialized by this instance. /// /// The initialized by this instance. - public IHostEnvironment HostingEnvironment { get; protected set; } + public IHostEnvironment Environment { get; protected set; } /// /// Gets a value indicating whether this object is disposed. @@ -168,10 +98,6 @@ protected static bool HasTypes(Type type, params Type[] types) /// protected virtual void OnDisposeManagedResources() { - if (ServiceProvider is ServiceProvider sp) - { - sp.Dispose(); - } Host?.Dispose(); } @@ -181,11 +107,6 @@ protected virtual void OnDisposeManagedResources() #if NET8_0_OR_GREATER protected virtual async ValueTask OnDisposeManagedResourcesAsync() { - if (ServiceProvider is ServiceProvider sp) - { - await sp.DisposeAsync(); - } - if (Host is IAsyncDisposable asyncDisposable) { await asyncDisposable.DisposeAsync(); diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs deleted file mode 100644 index 3ed8e1f..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostFixtureExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace Codebelt.Extensions.Xunit.Hosting -{ - /// - /// Extension methods for the interface. - /// - public static class HostFixtureExtensions - { - /// - /// 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; - } - } -} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs index fc46033..4dc5a31 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs @@ -8,18 +8,66 @@ namespace Codebelt.Extensions.Xunit.Hosting { /// - /// Represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection, should derive. + /// Represents the non-generic base class from its generic equivalent should derive. /// - /// The type of the object that implements the interface. /// + /// + /// + public abstract class HostTest : Test, IHostTest + { + /// + /// Initializes a new instance of the class. + /// + /// An implementation of the interface. + /// The of caller that ends up invoking this instance. + protected HostTest(ITestOutputHelper output = null, Type callerType = null) : base(output, callerType) + { + } + + /// + /// Adds and to this instance. + /// + /// The initialized by the . + /// The initialized by the . + public virtual void Configure(IConfiguration configuration, IHostEnvironment environment) + { + Configuration = configuration; + Environment = environment; + } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IHost Host { get; protected set; } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IConfiguration Configuration { get; protected set; } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IHostEnvironment Environment { get; protected set; } + } + + /// + /// Represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection, should derive. + /// + /// The type of the object that implements the interface. + /// + /// /// /// The class needed to be designed in this rather complex way, as this is the only way that xUnit supports a shared context. The need for shared context is theoretical at best, but it does opt-in for Scoped instances. - public abstract class HostTest : Test, IGenericHostTest, IClassFixture where T : class, IHostFixture + public abstract class HostTest : HostTest, IClassFixture where T : class, IGenericHostFixture { /// /// Initializes a new instance of the class. /// - /// An implementation of the interface. + /// An implementation of the interface. /// An implementation of the interface. /// The of caller that ends up invoking this instance. /// @@ -33,7 +81,7 @@ protected HostTest(T hostFixture, ITestOutputHelper output = null, Type callerTy /// Initializes a new instance of the class. /// /// A value indicating whether to skip the host fixture initialization. - /// An implementation of the interface. + /// An implementation of the interface. /// An implementation of the interface. /// The of caller that ends up invoking this instance. /// @@ -51,60 +99,7 @@ protected HostTest(bool skipHostFixtureInitialization, T hostFixture, ITestOutpu hostFixture.ConfigureHost(this); } Host = hostFixture.Host; - ServiceProvider = hostFixture.Host.Services; - Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); - } - - /// - /// 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 . - /// - /// The initialized by the . - public IHost Host { get; protected set; } - - /// - /// Gets the initialized by the . - /// - /// The initialized by the . - public IServiceProvider ServiceProvider { get; protected set; } - - /// - /// Gets the initialized by the . - /// - /// The initialized by the . - public IConfiguration Configuration - { - get; - private set; - } - - /// - /// Gets the initialized by the . - /// - /// The initialized by the . - public IHostEnvironment HostingEnvironment - { - get; - private set; - } - - /// - /// Adds and to this instance. - /// - /// The initialized by the . - /// The initialized by the . - public virtual void Configure(IConfiguration configuration, IHostEnvironment environment) - { - Configuration = configuration; - HostingEnvironment = environment; + Configure(hostFixture.Configuration, hostFixture.Environment); } /// diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IGenericHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IGenericHostFixture.cs new file mode 100644 index 0000000..92ce0d8 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/IGenericHostFixture.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Provides a way to use Microsoft Dependency Injection in unit tests. + /// + /// + public interface IGenericHostFixture : IHostFixture + { + /// + /// Gets or sets the delegate that adds services to the container. + /// + /// The delegate that adds services to the container. + Action ConfigureServicesCallback { get; set; } + + /// + /// Gets or sets the delegate that provides a way to override the defaults set up by . + /// + /// The delegate that provides a way to override the . + Action ConfigureHostCallback { get; set; } + + /// + /// Creates and configures the of this . + /// + /// The object that inherits from . + /// was added to support those cases where the caller is required in the host configuration. + void ConfigureHost(Test hostTest); + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs index 3a17b74..514ada7 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs @@ -1,39 +1,23 @@ using System; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace Codebelt.Extensions.Xunit.Hosting { /// - /// Provides a way to use Microsoft Dependency Injection in unit tests. + /// Provides a way to support app and lifetime management in unit tests. /// - /// - /// /// - /// - /// - /// - public interface IHostFixture : IServiceTest, IHostTest, IConfigurationTest, IHostingEnvironmentTest, IDisposable, IAsyncDisposable + /// + /// + /// + public interface IHostFixture : IConfigurationTest, IEnvironmentTest, IDisposable, IAsyncDisposable { /// - /// Gets or sets the delegate that adds services to the container. + /// Gets the initialized by the . /// - /// The delegate that adds services to the container. - Action ConfigureServicesCallback { get; set; } - - /// - /// Gets or sets the delegate that provides a way to override the defaults set up by . - /// - /// The delegate that provides a way to override the . - Action ConfigureHostCallback { get; set; } - - /// - /// Creates and configures the of this . - /// - /// The object that inherits from . - /// was added to support those cases where the caller is required in the host configuration. - void ConfigureHost(Test hostTest); + /// The initialized by the . + IHost Host { get; } /// /// Gets or sets the delegate that adds configuration and environment information to a . From 8db8a903f2d6a57b1fdfbe29edd7b9bf5d64cec8 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 17:38:07 +0200 Subject: [PATCH 06/15] :sparkles: added support for IHostApplicationBuilder (to complement IHostBuilder) --- .../IMinimalHostFixture.cs | 27 +++++++ .../Internal/MinimalHostTest.cs | 53 +++++++++++++ .../MinimalHostFixture.cs | 60 ++++++++++++++ .../MinimalHostFixtureExtensions.cs | 24 ++++++ .../MinimalHostTest.cs | 79 +++++++++++++++++++ .../MinimalHostTestFactory.cs | 36 +++++++++ 6 files changed, 279 insertions(+) create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/IMinimalHostFixture.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/Internal/MinimalHostTest.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixtureExtensions.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/MinimalHostTest.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting/MinimalHostTestFactory.cs diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IMinimalHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IMinimalHostFixture.cs new file mode 100644 index 0000000..bafe994 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/IMinimalHostFixture.cs @@ -0,0 +1,27 @@ +using Microsoft.Extensions.Hosting; +using System; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Provides a way to use Microsoft Dependency Injection in unit tests (minimal style). + /// + /// + /// + /// + public interface IMinimalHostFixture : IHostFixture + { + /// + /// Gets or sets the delegate that provides a way to override the defaults set up by . + /// + /// The delegate that provides a way to override the . + Action ConfigureHostCallback { get; set; } + + /// + /// Creates and configures the of this . + /// + /// The object that inherits from . + /// was added to support those cases where the caller is required in the host configuration. + void ConfigureHost(Test hostTest); + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/Internal/MinimalHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/Internal/MinimalHostTest.cs new file mode 100644 index 0000000..e6233b8 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/Internal/MinimalHostTest.cs @@ -0,0 +1,53 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting.Internal +{ + internal sealed class MinimalHostTest : MinimalHostTest + { + private readonly Action _serviceConfigurator; + private readonly Action _serviceConfiguratorWithContext; + private readonly Action _hostConfigurator; + private HostBuilderContext _hostBuilderContext; + + internal MinimalHostTest(Action serviceConfigurator, Action hostConfigurator, IMinimalHostFixture hostFixture) : base(true, hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + { + _serviceConfigurator = serviceConfigurator; + _hostConfigurator = hostConfigurator; + InitializeHostFixture(hostFixture); + } + + internal MinimalHostTest(Action serviceConfigurator, Action hostConfigurator, IMinimalHostFixture hostFixture) : base(true, hostFixture, callerType: serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + { + _serviceConfiguratorWithContext = serviceConfigurator; + _hostConfigurator = hostConfigurator; + InitializeHostFixture(hostFixture); + } + + private void InitializeHostFixture(IMinimalHostFixture hostFixture) + { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + Configure(hostFixture.Configuration, hostFixture.Environment); + } + + protected override void ConfigureHost(IHostApplicationBuilder hb) + { + _hostBuilderContext = new HostBuilderContext(hb.Properties); + _hostConfigurator?.Invoke(hb); + _serviceConfigurator?.Invoke(hb.Services); + _serviceConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc => + { + hbc.Configuration = hb.Configuration; + hbc.HostingEnvironment = hb.Environment; + return hbc; + }), hb.Services); + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs new file mode 100644 index 0000000..b2bd31c --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Provides a default implementation of the interface. + /// + /// + /// This is the "modern" minimal style implementation of . + public class MinimalHostFixture : HostFixture, IMinimalHostFixture + { + /// + /// Initializes a new instance of the class. + /// + public MinimalHostFixture() + { + } + + /// + /// Creates and configures the of this instance. + /// + /// The object that inherits from . + /// was added to support those cases where the caller is required in the host configuration. + /// + /// is null. + /// + /// + /// is not assignable from . + /// + public virtual void ConfigureHost(Test hostTest) + { + if (hostTest == null) { throw new ArgumentNullException(nameof(hostTest)); } + if (!HasTypes(hostTest.GetType(), typeof(MinimalHostTest<>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(MinimalHostTest<>), $"{nameof(hostTest)} is not assignable from MinimalHostTest."); } + + var hb = Microsoft.Extensions.Hosting.Host.CreateApplicationBuilder(new HostApplicationBuilderSettings() + { + EnvironmentName = "Development", + ApplicationName = hostTest.CallerType.Assembly.GetName().Name, + }); + + Configuration = hb.Configuration; + Environment = hb.Environment; + + ConfigureCallback(Configuration, Environment); + + ConfigureHostCallback(hb); + + Host = hb.Build(); + + HostRunnerCallback(Host); + } + + /// + /// Gets or sets the delegate that initializes the host application builder. + /// + /// The delegate that initializes the host application builder. + public Action ConfigureHostCallback { get; set; } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixtureExtensions.cs new file mode 100644 index 0000000..c9a1543 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixtureExtensions.cs @@ -0,0 +1,24 @@ +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Extension methods for the interface. + /// + public static class MinimalHostFixtureExtensions + { + /// + /// 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 IMinimalHostFixture hostFixture) + { + return hostFixture.Host != null && + hostFixture.ConfigureHostCallback != null && + hostFixture.ConfigureCallback != null; + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostTest.cs new file mode 100644 index 0000000..2eea7f9 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostTest.cs @@ -0,0 +1,79 @@ +using System; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Represents the non-generic base class from where its generic equivalent should derive. + /// + /// + public abstract class MinimalHostTest : HostTest + { + /// + /// Initializes a new instance of the class. + /// + /// An implementation of the interface. + /// The of caller that ends up invoking this instance. + protected MinimalHostTest(ITestOutputHelper output = null, Type callerType = null) : base(output, callerType) + { + } + + + /// + /// Provides a way to override the defaults. + /// + /// The that initializes an instance of . + protected virtual void ConfigureHost(IHostApplicationBuilder hb) + { + } + } + + /// + /// Represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection (minimal style), should derive. + /// + /// The type of the object that implements the interface. + /// + /// + /// The class needed to be designed in this rather complex way, as this is the only way that xUnit supports a shared context. The need for shared context is theoretical at best, but it does opt-in for Scoped instances. + public abstract class MinimalHostTest : MinimalHostTest, IClassFixture where T : class, IMinimalHostFixture + { + /// + /// Initializes a new instance of the class. + /// + /// An implementation of the interface. + /// An implementation of the interface. + /// The of caller that ends up invoking this instance. + /// + /// is null. + /// + protected MinimalHostTest(T hostFixture, ITestOutputHelper output = null, Type callerType = null) : this(false, hostFixture, output, callerType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A value indicating whether to skip the host fixture initialization. + /// An implementation of the interface. + /// An implementation of the interface. + /// The of caller that ends up invoking this instance. + /// + /// is null. + /// + protected MinimalHostTest(bool skipHostFixtureInitialization, T hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(output, callerType) + { + if (hostFixture == null) { throw new ArgumentNullException(nameof(hostFixture)); } + if (skipHostFixtureInitialization) { return; } + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + Configure(hostFixture.Configuration, hostFixture.Environment); + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostTestFactory.cs new file mode 100644 index 0000000..ff7cff7 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostTestFactory.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + /// + /// Provides a set of static methods for unit testing (minimal style). + /// + public static class MinimalHostTestFactory + { + /// + /// 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 IHostTest Create(Action serviceSetup = null, Action hostSetup = null, IMinimalHostFixture hostFixture = null) + { + return new Internal.MinimalHostTest(serviceSetup, hostSetup, hostFixture ?? new MinimalHostFixture()); + } + + /// + /// 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 IHostTest CreateWithHostBuilderContext(Action serviceSetup = null, Action hostSetup = null, IMinimalHostFixture hostFixture = null) + { + return new Internal.MinimalHostTest(serviceSetup, hostSetup, hostFixture ?? new MinimalHostFixture()); + } + } +} From 88e7f4a76df6e1eba0f2fa5f82809f8093f83aed Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 17:47:08 +0200 Subject: [PATCH 07/15] :memo: rewording but connected to 50666d880 --- src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs index 514ada7..60c8f4a 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs @@ -14,15 +14,15 @@ namespace Codebelt.Extensions.Xunit.Hosting public interface IHostFixture : IConfigurationTest, IEnvironmentTest, IDisposable, IAsyncDisposable { /// - /// Gets the initialized by the . + /// Gets the initialized by either the or . /// /// The initialized by the . IHost Host { get; } /// - /// Gets or sets the delegate that adds configuration and environment information to a . + /// Gets or sets the delegate that adds configuration and environment information to a . /// - /// 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 c06647a9ef8635515efcc59be6dbcc121d4b9af7 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 17:48:23 +0200 Subject: [PATCH 08/15] :arrow_up: bump dependencies --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index aae2599..0270c07 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ - + From 9bd5009cd5a878d13f9f5caf358dafbb73376eed Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 18:14:04 +0200 Subject: [PATCH 09/15] :building_construction: refactored for consistency (WebHostTest* and WebHostFixture*) --- .../AspNetCoreHostFixtureExtensions.cs | 24 ----- .../AspNetCoreHostTest.cs | 62 ------------- .../BlockingAspNetCoreHostFixture.cs | 20 ----- .../BlockingWebHostFixture.cs | 19 ++++ .../HttpClientExtensions.cs | 2 +- ...tCoreHostFixture.cs => IWebHostFixture.cs} | 4 +- .../IWebHostTest.cs | 4 +- .../Internal/WebHostTest.cs | 77 ++++++++++++++++ ...etCoreHostFixture.cs => WebHostFixture.cs} | 23 +++-- .../WebHostFixtureExtensions.cs | 24 +++++ .../WebHostTest.cs | 88 ++++++++----------- .../WebHostTestFactory.cs | 75 +++------------- 12 files changed, 183 insertions(+), 239 deletions(-) delete mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs delete mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs delete mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingAspNetCoreHostFixture.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingWebHostFixture.cs rename src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/{IAspNetCoreHostFixture.cs => IWebHostFixture.cs} (81%) create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Internal/WebHostTest.cs rename src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/{AspNetCoreHostFixture.cs => WebHostFixture.cs} (81%) create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostFixtureExtensions.cs diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs deleted file mode 100644 index a704a9f..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixtureExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore -{ - /// - /// Extension methods for the interface. - /// - public static class AspNetCoreHostFixtureExtensions - { - /// - /// 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) - { - var hasValidState = ((IHostFixture)fixture).HasValidState(); - return hasValidState && fixture.Application != null; - } - } -} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs deleted file mode 100644 index 005c2ad..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Hosting; -using Xunit.Abstractions; - -namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore -{ - /// - /// Represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection and depends on ASP.NET Core, should derive. - /// - /// The type of the object that implements the interface. - /// - /// - public abstract class AspNetCoreHostTest : HostTest, IWebHostTest where T : class, IAspNetCoreHostFixture - { - /// - /// Initializes a new instance of the class. - /// - /// An implementation of the interface. - /// An implementation of the interface. - /// The of caller that ends up invoking this instance. - protected AspNetCoreHostTest(T hostFixture, ITestOutputHelper output = null, Type callerType = null) : this(false, hostFixture, output, callerType) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// A value indicating whether to skip the host fixture initialization. - /// An implementation of the interface. - /// An implementation of the interface. - /// The of caller that ends up invoking this instance. - protected AspNetCoreHostTest(bool skipHostFixtureInitialization, T hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(skipHostFixtureInitialization, hostFixture, output, callerType) - { - if (skipHostFixtureInitialization) { return; } - 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); - } - - /// - /// Gets the initialized by the . - /// - /// The initialized by the . - public IApplicationBuilder Application { get; protected set; } - - /// - /// Configures the HTTP request pipeline. - /// - /// The type that provides the mechanisms to configure the HTTP request pipeline. - public abstract void ConfigureApplication(IApplicationBuilder app); - } -} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingAspNetCoreHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingAspNetCoreHostFixture.cs deleted file mode 100644 index 7cb6b7c..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingAspNetCoreHostFixture.cs +++ /dev/null @@ -1,20 +0,0 @@ -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(); - } - } -} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingWebHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingWebHostFixture.cs new file mode 100644 index 0000000..8489d43 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingWebHostFixture.cs @@ -0,0 +1,19 @@ +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 BlockingWebHostFixture : WebHostFixture + { + /// + /// Initializes a new instance of the class. + /// + public BlockingWebHostFixture() + { + HostRunnerCallback = host => host.Run(); + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HttpClientExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HttpClientExtensions.cs index 93f4a08..a112465 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.AspNetCore/IAspNetCoreHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IWebHostFixture.cs similarity index 81% rename from src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IAspNetCoreHostFixture.cs rename to src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IWebHostFixture.cs index 6af1478..23abfde 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IAspNetCoreHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IWebHostFixture.cs @@ -6,8 +6,8 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore /// /// Provides a way to use Microsoft Dependency Injection in unit tests tailored for ASP.NET Core. /// - /// - public interface IAspNetCoreHostFixture : IHostFixture, IPipelineTest + /// + public interface IWebHostFixture : IGenericHostFixture, IPipelineTest { /// /// Gets or sets the delegate that configures the HTTP request pipeline. diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IWebHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IWebHostTest.cs index d8ecf39..eb540e6 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IWebHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IWebHostTest.cs @@ -3,9 +3,9 @@ /// /// Represents the members needed for ASP.NET Core (including but not limited to MVC, Razor and related) testing. /// - /// + /// /// - public interface IWebHostTest : IGenericHostTest, IPipelineTest + public interface IWebHostTest : IHostTest, IPipelineTest { } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Internal/WebHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Internal/WebHostTest.cs new file mode 100644 index 0000000..8494f1b --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Internal/WebHostTest.cs @@ -0,0 +1,77 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Internal +{ + internal sealed class WebHostTest : WebHostTest + { + private readonly Action _pipelineConfigurator; + private readonly Action _serviceConfigurator; + private readonly Action _pipelineConfiguratorWithContext; + private readonly Action _serviceConfiguratorWithContext; + private readonly Action _hostConfigurator; + private HostBuilderContext _hostBuilderContext; + + internal WebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, IWebHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + { + _serviceConfigurator = serviceConfigurator; + _pipelineConfigurator = pipelineConfigurator; + _hostConfigurator = hostConfigurator; + InitializeHostFixture(hostFixture); + } + + internal WebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, IWebHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + { + _serviceConfiguratorWithContext = serviceConfigurator; + _pipelineConfiguratorWithContext = pipelineConfigurator; + _hostConfigurator = hostConfigurator; + InitializeHostFixture(hostFixture); + } + + private void InitializeHostFixture(IWebHostFixture hostFixture) + { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureApplicationCallback = ConfigureApplication; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + Application = hostFixture.Application; + Configure(hostFixture.Configuration, hostFixture.Environment); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + _pipelineConfigurator?.Invoke(app); + _pipelineConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc => + { + hbc.Configuration = Configuration; + hbc.HostingEnvironment = Environment; + return hbc; + }), app); + } + + protected override void ConfigureHost(IHostBuilder hb) + { + _hostBuilderContext = new HostBuilderContext(hb.Properties); + _hostConfigurator?.Invoke(hb); + } + + public override void ConfigureServices(IServiceCollection services) + { + _serviceConfigurator?.Invoke(services); + _serviceConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc => + { + hbc.Configuration = Configuration; + hbc.HostingEnvironment = Environment; + return hbc; + }), services); + services.AddFakeHttpContextAccessor(); + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostFixture.cs similarity index 81% rename from src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs rename to src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostFixture.cs index 3d26e8e..502ff04 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostFixture.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.StaticWebAssets; @@ -12,34 +11,34 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore { /// - /// Provides a default implementation of the interface. + /// Provides a default implementation of the interface. /// - /// - /// - public class AspNetCoreHostFixture : HostFixture, IAspNetCoreHostFixture + /// + /// + public class WebHostFixture : GenericHostFixture, IWebHostFixture { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public AspNetCoreHostFixture() + public WebHostFixture() { } /// /// Creates and configures the of this instance. /// - /// The object that inherits from . + /// The object that inherits from . /// was added to support those cases where the caller is required in the host configuration. /// /// is null. /// /// - /// is not assignable from . + /// is not assignable from . /// public override void ConfigureHost(Test hostTest) { if (hostTest == null) { throw new ArgumentNullException(nameof(hostTest)); } - if (!HasTypes(hostTest.GetType(), typeof(HostTest<>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(HostTest<>), $"{nameof(hostTest)} is not assignable from AspNetCoreHostTest."); } + if (!HasTypes(hostTest.GetType(), typeof(WebHostTest<>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(WebHostTest<>), $"{nameof(hostTest)} is not assignable from WebHostTest."); } var hb = new HostBuilder() .ConfigureWebHost(webBuilder => @@ -68,7 +67,7 @@ public override void ConfigureHost(Test hostTest) .ConfigureServices((context, services) => { Configuration = context.Configuration; - HostingEnvironment = context.HostingEnvironment; + Environment = context.HostingEnvironment; ConfigureServicesCallback(services); }) .Configure(app => @@ -92,7 +91,7 @@ public override void ConfigureHost(Test hostTest) Host = hb.Build(); - StartConfiguredHost(); + HostRunnerCallback(Host); } /// diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostFixtureExtensions.cs new file mode 100644 index 0000000..4f03f0a --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostFixtureExtensions.cs @@ -0,0 +1,24 @@ +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Extension methods for the interface. + /// + public static class WebHostFixtureExtensions + { + /// + /// 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 IWebHostFixture hostFixture) + { + var hasValidState = ((IGenericHostFixture)hostFixture).HasValidState(); + return hasValidState && hostFixture.ConfigureApplicationCallback != null && hostFixture.Application != null; + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs index dcc7a50..221fce0 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs @@ -1,37 +1,39 @@ using System; using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Xunit.Abstractions; namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore { - internal sealed class WebHostTest : AspNetCoreHostTest + /// + /// Represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection and depends on ASP.NET Core, should derive. + /// + /// The type of the object that implements the interface. + /// + /// + public abstract class WebHostTest : HostTest, IWebHostTest where T : class, IWebHostFixture { - private readonly Action _pipelineConfigurator; - private readonly Action _serviceConfigurator; - private readonly Action _pipelineConfiguratorWithContext; - private readonly Action _serviceConfiguratorWithContext; - private readonly Action _hostConfigurator; - private HostBuilderContext _hostBuilderContext; - - internal WebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, IAspNetCoreHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) - { - _serviceConfigurator = serviceConfigurator; - _pipelineConfigurator = pipelineConfigurator; - _hostConfigurator = hostConfigurator; - InitializeHostFixture(hostFixture); - } - - internal WebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, IAspNetCoreHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + /// + /// Initializes a new instance of the class. + /// + /// An implementation of the interface. + /// An implementation of the interface. + /// The of caller that ends up invoking this instance. + protected WebHostTest(T hostFixture, ITestOutputHelper output = null, Type callerType = null) : this(false, hostFixture, output, callerType) { - _serviceConfiguratorWithContext = serviceConfigurator; - _pipelineConfiguratorWithContext = pipelineConfigurator; - _hostConfigurator = hostConfigurator; - InitializeHostFixture(hostFixture); } - private new void InitializeHostFixture(IAspNetCoreHostFixture hostFixture) + /// + /// Initializes a new instance of the class. + /// + /// A value indicating whether to skip the host fixture initialization. + /// An implementation of the interface. + /// An implementation of the interface. + /// The of caller that ends up invoking this instance. + protected WebHostTest(bool skipHostFixtureInitialization, T hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(skipHostFixtureInitialization, hostFixture, output, callerType) { + if (hostFixture == null) { throw new ArgumentNullException(nameof(hostFixture)); } + if (skipHostFixtureInitialization) { return; } if (!hostFixture.HasValidState()) { hostFixture.ConfigureHostCallback = ConfigureHost; @@ -41,38 +43,20 @@ internal WebHostTest(Action serviceConfi hostFixture.ConfigureHost(this); } Host = hostFixture.Host; - ServiceProvider = hostFixture.Host.Services; Application = hostFixture.Application; - Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + Configure(hostFixture.Configuration, hostFixture.Environment); } - public override void ConfigureApplication(IApplicationBuilder app) - { - _pipelineConfigurator?.Invoke(app); - _pipelineConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc => - { - hbc.Configuration = Configuration; - hbc.HostingEnvironment = HostingEnvironment; - return hbc; - }), app); - } + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IApplicationBuilder Application { get; protected set; } - protected override void ConfigureHost(IHostBuilder hb) - { - _hostBuilderContext = new HostBuilderContext(hb.Properties); - _hostConfigurator?.Invoke(hb); - } - - public override void ConfigureServices(IServiceCollection services) - { - _serviceConfigurator?.Invoke(services); - _serviceConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc => - { - hbc.Configuration = Configuration; - hbc.HostingEnvironment = HostingEnvironment; - return hbc; - }), services); - services.AddFakeHttpContextAccessor(); - } + /// + /// Configures the HTTP request pipeline. + /// + /// The type that provides the mechanisms to configure the HTTP request pipeline. + public abstract void ConfigureApplication(IApplicationBuilder app); } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTestFactory.cs index 8ce2e82..23536cf 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTestFactory.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTestFactory.cs @@ -1,6 +1,7 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Internal; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; @@ -20,11 +21,11 @@ public static class WebHostTestFactory /// 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. - [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) + public static IWebHostTest Create(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, IWebHostFixture hostFixture = null) { - return Create(serviceSetup, pipelineSetup, hostSetup, null); + return new WebHostTest(serviceSetup, pipelineSetup, hostSetup, hostFixture ?? new WebHostFixture()); } /// @@ -33,51 +34,11 @@ 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 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) + public static IWebHostTest CreateWithHostBuilderContext(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, IWebHostFixture 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 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); + return new WebHostTest(serviceSetup, pipelineSetup, hostSetup, hostFixture ?? new WebHostFixture()); } /// @@ -87,9 +48,9 @@ public static Task RunAsync(Action serv /// 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. + /// 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, IAspNetCoreHostFixture hostFixture = null) + public static async Task RunAsync(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, Func> responseFactory = null, IWebHostFixture hostFixture = null) { using var client = Create(serviceSetup, pipelineSetup, hostSetup, hostFixture).Host.GetTestClient(); return await client.ToHttpResponseMessageAsync(responseFactory).ConfigureAwait(false); @@ -102,23 +63,9 @@ public static async Task RunAsync(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. - [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) + public static async Task RunWithHostBuilderContextAsync(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, Func> responseFactory = null, IWebHostFixture hostFixture = null) { using var client = CreateWithHostBuilderContext(serviceSetup, pipelineSetup, hostSetup, hostFixture).Host.GetTestClient(); return await client.ToHttpResponseMessageAsync(responseFactory).ConfigureAwait(false); From 72f39e61b2ac428376949b8ac44e74054687ab94 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 18:23:55 +0200 Subject: [PATCH 10/15] :sparkles: added support for IHostApplicationBuilder (to complement IHostBuilder) --- .../HostBuilderApplicationExtensions.cs | 26 +++++++ .../IMinimalWebHostFixture.cs | 19 +++++ .../Internal/MinimalWebHostTest.cs | 72 ++++++++++++++++++ .../MinimalWebHostFixture.cs | 76 +++++++++++++++++++ .../MinimalWebHostFixtureExtensions.cs | 24 ++++++ .../MinimalWebHostTest.cs | 67 ++++++++++++++++ .../MinimalWebHostTestFactory.cs | 74 ++++++++++++++++++ 7 files changed, 358 insertions(+) create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HostBuilderApplicationExtensions.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IMinimalWebHostFixture.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Internal/MinimalWebHostTest.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostFixture.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostFixtureExtensions.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTest.cs create mode 100644 src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTestFactory.cs diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HostBuilderApplicationExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HostBuilderApplicationExtensions.cs new file mode 100644 index 0000000..338f1ba --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/HostBuilderApplicationExtensions.cs @@ -0,0 +1,26 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Provides extension methods for . + /// + public static class HostBuilderApplicationExtensions + { + /// + /// Converts an to an . + /// + /// The to convert. + /// The instance. + /// + /// is not a . + /// + public static IHostBuilder ToHostBuilder(this IHostApplicationBuilder builder) + { + if (builder is WebApplicationBuilder webAppBuilder) { return webAppBuilder.Host; } + throw new ArgumentException($"The provided IHostApplicationBuilder is not a {nameof(WebApplicationBuilder)}.", nameof(builder)); + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IMinimalWebHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IMinimalWebHostFixture.cs new file mode 100644 index 0000000..79102b4 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/IMinimalWebHostFixture.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.AspNetCore.Builder; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Provides a way to use Microsoft Dependency Injection in unit tests (minimal style). + /// + /// + /// + public interface IMinimalWebHostFixture : IMinimalHostFixture, IPipelineTest + { + /// + /// Gets or sets the delegate that configures the HTTP request pipeline. + /// + /// The delegate that configures the HTTP request pipeline. + Action ConfigureApplicationCallback { get; set; } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Internal/MinimalWebHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Internal/MinimalWebHostTest.cs new file mode 100644 index 0000000..b7711f8 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Internal/MinimalWebHostTest.cs @@ -0,0 +1,72 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Internal +{ + internal sealed class MinimalWebHostTest : MinimalWebHostTest + { + private readonly Action _pipelineConfigurator; + private readonly Action _serviceConfigurator; + private readonly Action _pipelineConfiguratorWithContext; + private readonly Action _serviceConfiguratorWithContext; + private readonly Action _hostConfigurator; + private HostBuilderContext _hostBuilderContext; + + internal MinimalWebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, IMinimalWebHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + { + _serviceConfigurator = serviceConfigurator; + _pipelineConfigurator = pipelineConfigurator; + _hostConfigurator = hostConfigurator; + InitializeHostFixture(hostFixture); + } + + internal MinimalWebHostTest(Action serviceConfigurator, Action pipelineConfigurator, Action hostConfigurator, IMinimalWebHostFixture hostFixture) : base(true, hostFixture, callerType: pipelineConfigurator?.Target?.GetType() ?? serviceConfigurator?.Target?.GetType() ?? hostConfigurator?.Target?.GetType()) + { + _serviceConfiguratorWithContext = serviceConfigurator; + _pipelineConfiguratorWithContext = pipelineConfigurator; + _hostConfigurator = hostConfigurator; + InitializeHostFixture(hostFixture); + } + + private void InitializeHostFixture(IMinimalWebHostFixture hostFixture) + { + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureApplicationCallback = ConfigureApplication; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + Application = hostFixture.Application; + Configure(hostFixture.Configuration, hostFixture.Environment); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + _pipelineConfigurator?.Invoke(app); + _pipelineConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc => + { + hbc.Configuration = Configuration; + hbc.HostingEnvironment = Environment; + return hbc; + }), app); + } + + protected override void ConfigureHost(IHostApplicationBuilder hb) + { + _hostBuilderContext = new HostBuilderContext(hb.Properties); + _hostConfigurator?.Invoke(hb); + _serviceConfigurator?.Invoke(hb.Services); + _serviceConfiguratorWithContext?.Invoke(Tweaker.Adjust(_hostBuilderContext, hbc => + { + hbc.Configuration = hb.Configuration; + hbc.HostingEnvironment = hb.Environment; + return hbc; + }), hb.Services); + hb.Services.AddFakeHttpContextAccessor(); + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostFixture.cs new file mode 100644 index 0000000..05e2ce9 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostFixture.cs @@ -0,0 +1,76 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Provides a default implementation of the interface. + /// + /// + /// + /// This is the "modern" minimal style implementation of . + public class MinimalWebHostFixture : MinimalHostFixture, IMinimalWebHostFixture + { + /// + /// Initializes a new instance of the class. + /// + public MinimalWebHostFixture() + { + } + + /// + /// Creates and configures the of this instance. + /// + /// The object that inherits from . + /// was added to support those cases where the caller is required in the host configuration. + /// + /// is null. + /// + /// + /// is not assignable from . + /// + public override void ConfigureHost(Test hostTest) + { + if (hostTest == null) { throw new ArgumentNullException(nameof(hostTest)); } + if (!HasTypes(hostTest.GetType(), typeof(MinimalWebHostTest<>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(MinimalWebHostTest<>), $"{nameof(hostTest)} is not assignable from MinimalWebHostTest."); } + + var hb = WebApplication.CreateBuilder(new WebApplicationOptions() + { + EnvironmentName = "Development", + ApplicationName = hostTest.CallerType.Assembly.GetName().Name + }); + + hb.WebHost.UseTestServer(o => o.PreserveExecutionContext = true); + + Configuration = hb.Configuration; + Environment = hb.Environment; + + ConfigureCallback(Configuration, Environment); + + ConfigureHostCallback(hb); + + var webApplication = hb.Build(); + + ConfigureApplicationCallback(webApplication); + Application = webApplication; + + Host = webApplication; + + HostRunnerCallback(Host); + } + + /// + /// Gets or sets the delegate that configures the HTTP request pipeline. + /// + /// The delegate that configures the HTTP request pipeline. + public Action ConfigureApplicationCallback { get; set; } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IApplicationBuilder Application { get; protected set; } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostFixtureExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostFixtureExtensions.cs new file mode 100644 index 0000000..110c191 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostFixtureExtensions.cs @@ -0,0 +1,24 @@ +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Extension methods for the interface. + /// + public static class MinimalWebHostFixtureExtensions + { + /// + /// 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 IMinimalWebHostFixture hostFixture) + { + var hasValidState = ((IMinimalHostFixture)hostFixture).HasValidState(); + return hasValidState && hostFixture.ConfigureApplicationCallback != null && hostFixture.Application != null; + } + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTest.cs new file mode 100644 index 0000000..028591a --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTest.cs @@ -0,0 +1,67 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection and depends on ASP.NET Core (minimal style), should derive. + /// + /// The type of the object that implements the interface. + /// + /// + /// + /// The class needed to be designed in this rather complex way, as this is the only way that xUnit supports a shared context. The need for shared context is theoretical at best, but it does opt-in for Scoped instances. + public abstract class MinimalWebHostTest : MinimalHostTest, IWebHostTest, IClassFixture where T : class, IMinimalWebHostFixture + { + /// + /// Initializes a new instance of the class. + /// + /// An implementation of the interface. + /// An implementation of the interface. + /// The of caller that ends up invoking this instance. + protected MinimalWebHostTest(T hostFixture, ITestOutputHelper output = null, Type callerType = null) : this(false, hostFixture, output, callerType) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A value indicating whether to skip the host fixture initialization. + /// An implementation of the interface. + /// An implementation of the interface. + /// The of caller that ends up invoking this instance. + /// + /// is null. + /// + protected MinimalWebHostTest(bool skipHostFixtureInitialization, T hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(output, callerType) + { + if (hostFixture == null) { throw new ArgumentNullException(nameof(hostFixture)); } + if (skipHostFixtureInitialization) { return; } + if (!hostFixture.HasValidState()) + { + hostFixture.ConfigureCallback = Configure; + hostFixture.ConfigureHostCallback = ConfigureHost; + hostFixture.ConfigureApplicationCallback = ConfigureApplication; + hostFixture.ConfigureHost(this); + } + Host = hostFixture.Host; + Application = hostFixture.Application; + Configure(hostFixture.Configuration, hostFixture.Environment); + } + + /// + /// Gets the initialized by the . + /// + /// The initialized by the . + public IApplicationBuilder Application { get; protected set; } + + /// + /// Configures the HTTP request pipeline. + /// + /// The type that provides the mechanisms to configure the HTTP request pipeline. + public abstract void ConfigureApplication(IApplicationBuilder app); + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTestFactory.cs new file mode 100644 index 0000000..c13e3c1 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTestFactory.cs @@ -0,0 +1,74 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Internal; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + /// + /// Provides a set of static methods for ASP.NET Core (including, but not limited to MVC, Razor and related) unit testing (minimal style). + /// + /// . + public static class MinimalWebHostTestFactory + { + /// + /// 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 Create(Action serviceSetup = null, Action pipelineSetup = null, Action hostSetup = null, IMinimalWebHostFixture hostFixture = null) + { + return new MinimalWebHostTest(serviceSetup, pipelineSetup, hostSetup, hostFixture ?? new MinimalWebHostFixture()); + } + + /// + /// 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, IMinimalWebHostFixture hostFixture = null) + { + return new MinimalWebHostTest(serviceSetup, pipelineSetup, hostSetup, hostFixture ?? new MinimalWebHostFixture()); + } + + /// + /// 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 ("/"). + /// 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, IMinimalWebHostFixture hostFixture = null) + { + using var client = Create(serviceSetup, pipelineSetup, hostSetup, hostFixture).Host.GetTestClient(); + return await client.ToHttpResponseMessageAsync(responseFactory).ConfigureAwait(false); + } + + /// + /// 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, IMinimalWebHostFixture hostFixture = null) + { + using var client = CreateWithHostBuilderContext(serviceSetup, pipelineSetup, hostSetup, hostFixture).Host.GetTestClient(); + return await client.ToHttpResponseMessageAsync(responseFactory).ConfigureAwait(false); + } + } +} From fa324b65815116a0a393986e17c7bf6a2c12fc97 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 18:26:09 +0200 Subject: [PATCH 11/15] :memo: added additional seealso doc to connect with 8db8a90 --- src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs index b2bd31c..b89b747 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs @@ -7,6 +7,7 @@ namespace Codebelt.Extensions.Xunit.Hosting /// Provides a default implementation of the interface. /// /// + /// /// This is the "modern" minimal style implementation of . public class MinimalHostFixture : HostFixture, IMinimalHostFixture { From cdd4d420ff5e8b9da0f387644abdf24c9d202c3f Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 18:26:42 +0200 Subject: [PATCH 12/15] :white_check_mark: updated test to reflect changes --- .../Assets/InvalidHostTest.cs | 2 +- .../Assets/ValidHostTest.cs | 4 +- .../MinimalMvcWebHostTestTest.cs | 59 +++++++ .../MinimalWebHostTestFactoryTest.cs | 137 +++++++++++++++ .../MinimalWebHostTestTest.cs | 166 ++++++++++++++++++ ...eHostTestTest.cs => MvcWebHostTestTest.cs} | 8 +- ...stFixtureTest.cs => WebHostFixtureTest.cs} | 14 +- .../WebHostTestFactoryTest.cs | 12 +- ...CoreHostTestTest.cs => WebHostTestTest.cs} | 28 ++- .../Assets/InvalidHostTest.cs | 2 +- .../Assets/MinimalValidHostTest.cs | 13 ++ .../Assets/ValidHostTest.cs | 4 +- ...xtureTest.cs => GenericHostFixtureTest.cs} | 14 +- ...tFactoryTest.cs => HostTestFactoryTest.cs} | 11 +- .../HostTestTest.cs | 26 ++- .../MinimalHostFixtureTest.cs | 58 ++++++ .../MinimalHostTestFactoryTest.cs | 46 +++++ .../MinimalHostTestTest.cs | 100 +++++++++++ 18 files changed, 643 insertions(+), 61 deletions(-) create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalMvcWebHostTestTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalWebHostTestFactoryTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalWebHostTestTest.cs rename test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/{MvcAspNetCoreHostTestTest.cs => MvcWebHostTestTest.cs} (77%) rename test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/{AspNetCoreHostFixtureTest.cs => WebHostFixtureTest.cs} (77%) rename test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/{AspNetCoreHostTestTest.cs => WebHostTestTest.cs} (83%) create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/MinimalValidHostTest.cs rename test/Codebelt.Extensions.Xunit.Hosting.Tests/{HostFixtureTest.cs => GenericHostFixtureTest.cs} (75%) rename test/Codebelt.Extensions.Xunit.Hosting.Tests/{GenericHostTestFactoryTest.cs => HostTestFactoryTest.cs} (78%) create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostFixtureTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostTestFactoryTest.cs create mode 100644 test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostTestTest.cs diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/InvalidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/InvalidHostTest.cs index c2ba881..6b98e35 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/InvalidHostTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/InvalidHostTest.cs @@ -2,7 +2,7 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets { - public class InvalidHostTest : Test, IClassFixture where T : class, IHostFixture + public class InvalidHostTest : Test, IClassFixture where T : class, IGenericHostFixture { public InvalidHostTest(T hostFixture) { 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..7b10a4a 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ValidHostTest.cs @@ -3,9 +3,9 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets { - public class ValidHostTest : AspNetCoreHostTest + public class ValidHostTest : WebHostTest { - public ValidHostTest(AspNetCoreHostFixture hostFixture) : base(hostFixture) + public ValidHostTest(WebHostFixture hostFixture) : base(hostFixture) { } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalMvcWebHostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalMvcWebHostTestTest.cs new file mode 100644 index 0000000..268ec93 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalMvcWebHostTestTest.cs @@ -0,0 +1,59 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + public class MinimalMvcWebHostTestTest : MinimalWebHostTest + { + private readonly MinimalWebHostFixture _hostFixture; + private readonly HttpClient _client; + + public MinimalMvcWebHostTestTest(MinimalWebHostFixture hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(hostFixture, output, callerType) + { + hostFixture.Host.Services.GetRequiredService().TestOutput = output; + _hostFixture = hostFixture; + _client = hostFixture.Host.GetTestClient(); + } + + [Fact] + public async Task GetTestAsync() + { + var response = await _client.GetAsync("/Fake"); + response.EnsureSuccessStatusCode(); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal("Unit Test", body); + } + + [Fact] + public async Task GetTestAsync2() + { + var response = await _client.GetAsync("/Fake"); + response.EnsureSuccessStatusCode(); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal("Unit Test", body); + } + + protected override void ConfigureHost(IHostApplicationBuilder hb) + { + hb.Services.AddControllers() + .AddApplicationPart(typeof(FakeController).Assembly); + + hb.Services.AddXunitTestLoggingOutputHelperAccessor(); + hb.Services.AddXunitTestLogging(TestOutput); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.UseRouting(); + app.UseEndpoints(routes => { routes.MapControllers(); }); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalWebHostTestFactoryTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalWebHostTestFactoryTest.cs new file mode 100644 index 0000000..3a0f7fc --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalWebHostTestFactoryTest.cs @@ -0,0 +1,137 @@ +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 Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + public class MinimalWebHostTestFactoryTest : Test + { + public MinimalWebHostTestFactoryTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Create_ShouldThrowSecurityException_DueToBlockingAspNetCoreHostFixture() + { + Assert.Throws(() => MinimalWebHostTestFactory.Create( + services => + { + services.AddXunitTestLogging(TestOutput); + services.AddAuthorization(o => + { + o.AddPolicy("Test", _ => throw new SecurityException()); + }); + }, + app => + { + var policy = app.ApplicationServices.GetRequiredService(); + })); + } + + [Fact] + public void Create_CallerTypeShouldHaveDeclaringTypeOfMiddlewareTestFactoryTest() + { + Type sut1 = GetType(); + string sut2 = null; + var middleware = MinimalWebHostTestFactory.Create(Assert.NotNull, Assert.NotNull, host => + { + var hb = host.ToHostBuilder(); + hb.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 MinimalWebHostTestFactory.RunAsync(Assert.NotNull, Assert.NotNull, host => + { + var hb = host.ToHostBuilder(); + hb.ConfigureAppConfiguration((context, _) => + { + TestOutput.WriteLine(context.HostingEnvironment.ApplicationName); + Assert.Equal(GetType().Assembly.GetName().Name, context.HostingEnvironment.ApplicationName); + }); + }); + } + + [Fact] + public Task RunWithHostBuilderContextAsync_ShouldHaveApplicationNameEqualToThisAssembly_WithHostBuilderContext() + { + return MinimalWebHostTestFactory.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 => + { + var hb = host.ToHostBuilder(); + hb.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 MinimalWebHostTestFactory.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()); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalWebHostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalWebHostTestTest.cs new file mode 100644 index 0000000..792a2ab --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MinimalWebHostTestTest.cs @@ -0,0 +1,166 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets; +using Cuemon.Extensions.IO; +using Cuemon.Messaging; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore +{ + public class MinimalWebHostTestTest : MinimalWebHostTest + { + private readonly IServiceProvider _provider; + private readonly IApplicationBuilder _pipeline; + + public MinimalWebHostTestTest(MinimalWebHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _pipeline = hostFixture.Application; + _provider = hostFixture.Host.Services; + _provider.GetRequiredService().TestOutput = output; + } + + [Fact] + public async Task ShouldHaveResultOfBoolMiddlewareInBody() + { + var context = _provider.GetRequiredService().HttpContext; + var options = _provider.GetRequiredService>(); + var pipeline = _pipeline.Build(); + + Assert.Equal("Hello awesome developers!", context!.Response.Body.ToEncodedString(o => o.LeaveOpen = true)); + + var logger = _pipeline.ApplicationServices.GetRequiredService>(); + logger.LogInformation("Hello from {0}", nameof(ShouldHaveResultOfBoolMiddlewareInBody)); + + await pipeline(context); + + Assert.Equal("A:True, B:False, C:True, D:False, E:True, F:False", context.Response.Body.ToEncodedString()); + + Assert.True(options.Value.A); + Assert.False(options.Value.B); + Assert.True(options.Value.C); + Assert.False(options.Value.D); + Assert.True(options.Value.E); + Assert.False(options.Value.F); + } + +#if NET9_0_OR_GREATER + [Fact] + public void ShouldThrowInvalidOperationException_BecauseOneOfTheServicesIsScoped() + { + var ex = Assert.Throws(() => _provider.GetServices()); + + TestOutput.WriteLine(ex.Message); + + Assert.Contains("from root provider because it requires scoped service", ex.Message); + } +#endif + + [Fact] + public void ShouldHaveAccessToCorrelationTokens_UsingScopedProvider() + { + using var scope = _provider.CreateScope(); + + var firstRequest = scope.ServiceProvider.GetServices().ToList(); + var secondRequest = scope.ServiceProvider.GetServices().ToList(); + + TestOutput.WriteLine("----"); + TestOutput.WriteLines(firstRequest); + TestOutput.WriteLine("----"); + TestOutput.WriteLines(secondRequest); + + Assert.Equal(3, firstRequest.Count); + Assert.Equal(3, secondRequest.Count); + + Assert.Same(firstRequest[0], secondRequest[0]); + Assert.NotSame(firstRequest[1], secondRequest[1]); + Assert.Same(firstRequest[2], secondRequest[2]); + + Assert.Equal(firstRequest[0].CorrelationId, secondRequest[0].CorrelationId); + Assert.NotEqual(firstRequest[1].CorrelationId, secondRequest[1].CorrelationId); + Assert.Equal(firstRequest[2].CorrelationId, secondRequest[2].CorrelationId); + } + + [Fact] + public void ShouldHaveAccessToCorrelationTokens_UsingRequestServices() // reference: https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http/src/Features/RequestServicesFeature.cs + { + var context = _provider.GetRequiredService().HttpContext!; + + var firstRequest = context.RequestServices.GetServices().ToList(); + var secondRequest = context.RequestServices.GetServices().ToList(); + + TestOutput.WriteLine("----"); + TestOutput.WriteLines(firstRequest); + TestOutput.WriteLine("----"); + TestOutput.WriteLines(secondRequest); + + Assert.Equal(3, firstRequest.Count); + Assert.Equal(3, secondRequest.Count); + + Assert.Same(firstRequest[0], secondRequest[0]); + Assert.NotSame(firstRequest[1], secondRequest[1]); + Assert.Same(firstRequest[2], secondRequest[2]); + + Assert.Equal(firstRequest[0].CorrelationId, secondRequest[0].CorrelationId); + Assert.NotEqual(firstRequest[1].CorrelationId, secondRequest[1].CorrelationId); + Assert.Equal(firstRequest[2].CorrelationId, secondRequest[2].CorrelationId); + } + + [Fact] + public void ShouldLogToXunitTestLogging() + { + var context = _provider.GetRequiredService().HttpContext; + var logger = _pipeline.ApplicationServices.GetRequiredService>(); + logger.LogInformation("Hello stranger {0}", nameof(ShouldLogToXunitTestLogging)); + var store = _pipeline.ApplicationServices.GetRequiredService>().GetTestStore(); + var entry = store.Query(entry => entry.Message.Contains("Hello stranger", StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); + + Assert.NotNull(entry); + Assert.Equal("Information: Hello stranger ShouldLogToXunitTestLogging", entry.Message); + } + + [Fact] + public void Test_VerifyAbstractions() + { + using var hostTest = WebHostTestFactory.Create(); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + } + + protected override void ConfigureHost(IHostApplicationBuilder hb) + { + hb.Services.AddFakeHttpContextAccessor(); + hb.Services.Configure(o => + { + o.A = true; + o.C = true; + o.E = true; + }); + hb.Services.AddXunitTestLoggingOutputHelperAccessor(); + hb.Services.AddXunitTestLogging(TestOutput); + + hb.Services.AddSingleton(); + hb.Services.AddTransient(); + hb.Services.AddScoped(); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.ApplicationServices.GetRequiredService>().LogInformation(nameof(ConfigureApplication)); + app.UseMiddleware(); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcAspNetCoreHostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcWebHostTestTest.cs similarity index 77% rename from test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcAspNetCoreHostTestTest.cs rename to test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcWebHostTestTest.cs index bbaeb66..e5d51b6 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcAspNetCoreHostTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/MvcWebHostTestTest.cs @@ -10,14 +10,14 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore { - public class MvcAspNetCoreHostTestTest : AspNetCoreHostTest + public class MvcWebHostTestTest : WebHostTest { - private readonly AspNetCoreHostFixture _hostFixture; + private readonly WebHostFixture _hostFixture; private readonly HttpClient _client; - public MvcAspNetCoreHostTestTest(AspNetCoreHostFixture hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(hostFixture, output, callerType) + public MvcWebHostTestTest(WebHostFixture hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(hostFixture, output, callerType) { - hostFixture.ServiceProvider.GetRequiredService().TestOutput = output; + hostFixture.Host.Services.GetRequiredService().TestOutput = output; _hostFixture = hostFixture; _client = hostFixture.Host.GetTestClient(); } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostFixtureTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostFixtureTest.cs similarity index 77% rename from test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostFixtureTest.cs rename to test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostFixtureTest.cs index 243fa39..8e63d94 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostFixtureTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostFixtureTest.cs @@ -6,9 +6,9 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore { - public class AspNetCoreHostFixtureTest : Test + public class WebHostFixtureTest : Test { - public AspNetCoreHostFixtureTest(ITestOutputHelper output) : base(output) + public WebHostFixtureTest(ITestOutputHelper output) : base(output) { } @@ -16,7 +16,7 @@ public AspNetCoreHostFixtureTest(ITestOutputHelper output) : base(output) public void ConfigureHost_NullHostTest_ThrowsArgumentNullException() { // Arrange - var fixture = new AspNetCoreHostFixture(); + var fixture = new WebHostFixture(); // Act & Assert Assert.Throws(() => fixture.ConfigureHost(null)); @@ -26,8 +26,8 @@ public void ConfigureHost_NullHostTest_ThrowsArgumentNullException() public void ConfigureHost_InvalidHostTestType_ThrowsArgumentOutOfRangeException() { // Arrange - var fixture = new AspNetCoreHostFixture(); - var invalidHostTest = new InvalidHostTest(fixture); + var fixture = new WebHostFixture(); + var invalidHostTest = new InvalidHostTest(fixture); // Act & Assert Assert.Throws(() => fixture.ConfigureHost(invalidHostTest)); @@ -37,7 +37,7 @@ public void ConfigureHost_InvalidHostTestType_ThrowsArgumentOutOfRangeException( public void ConfigureApplicationCallback_SetAndGet_ReturnsCorrectValue() { // Arrange - var fixture = new AspNetCoreHostFixture(); + var fixture = new WebHostFixture(); Action callback = app => { }; // Act @@ -51,7 +51,7 @@ public void ConfigureApplicationCallback_SetAndGet_ReturnsCorrectValue() public void ConfigureHost_ValidHostTest_ConfiguresHostCorrectly() { // Arrange - var fixture = new AspNetCoreHostFixture(); + var fixture = new WebHostFixture(); var hostTest = new ValidHostTest(fixture); // Act diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestFactoryTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestFactoryTest.cs index 1719561..79a122c 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestFactoryTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestFactoryTest.cs @@ -47,7 +47,7 @@ public void Create_ShouldThrowSecurityException_DueToBlockingAspNetCoreHostFixtu o.ValidateScopes = false; }); }, - new BlockingAspNetCoreHostFixture())); + new BlockingWebHostFixture())); } [Fact] @@ -73,7 +73,7 @@ public void Create_ShouldCaptureSecurityException_DueToNonBlockingAspNetCoreHost }); })) { - var loggerStore = startup.ServiceProvider.GetRequiredService>().GetTestStore(null); + var loggerStore = startup.Host.Services.GetRequiredService>().GetTestStore(null); 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); @@ -107,7 +107,7 @@ public Task RunAsync_ShouldHaveApplicationNameEqualToThisAssembly() TestOutput.WriteLine(context.HostingEnvironment.ApplicationName); Assert.Equal(GetType().Assembly.GetName().Name, context.HostingEnvironment.ApplicationName); }); - }, hostFixture: null); + }); } [Fact] @@ -136,8 +136,7 @@ public Task RunWithHostBuilderContextAsync_ShouldHaveApplicationNameEqualToThisA TestOutput.WriteLine(context.HostingEnvironment.ApplicationName); Assert.Equal(GetType().Assembly.GetName().Name, context.HostingEnvironment.ApplicationName); }); - }, - hostFixture: null); + }); } [Fact] @@ -168,8 +167,7 @@ public async Task RunAsync_ShouldWorkWithXunitTestLogging() Thread.Sleep(400); return context.Response.WriteAsync("Hello World!"); }); - }, - hostFixture: null).ConfigureAwait(false); + }).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.AspNetCore.Tests/AspNetCoreHostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestTest.cs similarity index 83% rename from test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs rename to test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestTest.cs index 1dd81c9..d2211d1 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/WebHostTestTest.cs @@ -14,15 +14,15 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore { - public class AspNetCoreHostTestTest : AspNetCoreHostTest + public class WebHostTestTest : WebHostTest { private readonly IServiceProvider _provider; private readonly IApplicationBuilder _pipeline; - public AspNetCoreHostTestTest(AspNetCoreHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + public WebHostTestTest(WebHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) { _pipeline = hostFixture.Application; - _provider = hostFixture.ServiceProvider; + _provider = hostFixture.Host.Services; _provider.GetRequiredService().TestOutput = output; } @@ -35,7 +35,7 @@ public async Task ShouldHaveResultOfBoolMiddlewareInBody() Assert.Equal("Hello awesome developers!", context!.Response.Body.ToEncodedString(o => o.LeaveOpen = true)); - var logger = _pipeline.ApplicationServices.GetRequiredService>(); + var logger = _pipeline.ApplicationServices.GetRequiredService>(); logger.LogInformation("Hello from {0}", nameof(ShouldHaveResultOfBoolMiddlewareInBody)); await pipeline(context); @@ -116,34 +116,32 @@ public void ShouldHaveAccessToCorrelationTokens_UsingRequestServices() // refere public void ShouldLogToXunitTestLogging() { var context = _provider.GetRequiredService().HttpContext; - var logger = _pipeline.ApplicationServices.GetRequiredService>(); - logger.LogInformation("Hello from {0}", nameof(ShouldLogToXunitTestLogging)); - var store = _pipeline.ApplicationServices.GetRequiredService>().GetTestStore(); - var entry = store.Query(entry => entry.Message.Contains("Hello from", StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); + var logger = _pipeline.ApplicationServices.GetRequiredService>(); + logger.LogInformation("Hello stranger {0}", nameof(ShouldLogToXunitTestLogging)); + var store = _pipeline.ApplicationServices.GetRequiredService>().GetTestStore(); + var entry = store.Query(entry => entry.Message.Contains("Hello stranger", StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); Assert.NotNull(entry); - Assert.Equal("Information: Hello from ShouldLogToXunitTestLogging", entry.Message); + Assert.Equal("Information: Hello stranger ShouldLogToXunitTestLogging", entry.Message); } [Fact] public void Test_VerifyAbstractions() { - using var hostTest = WebHostTestFactory.Create(hostFixture: null); + using var hostTest = WebHostTestFactory.Create(); Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); - Assert.IsAssignableFrom(hostTest); - Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); - Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); - Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); } public override void ConfigureApplication(IApplicationBuilder app) { - app.ApplicationServices.GetRequiredService>().LogInformation(nameof(ConfigureApplication)); + app.ApplicationServices.GetRequiredService>().LogInformation(nameof(ConfigureApplication)); app.UseMiddleware(); } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/InvalidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/InvalidHostTest.cs index 9bc2e92..847c6ed 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/InvalidHostTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/InvalidHostTest.cs @@ -2,7 +2,7 @@ namespace Codebelt.Extensions.Xunit.Hosting.Assets { - public class InvalidHostTest : Test, IClassFixture where T : class, IHostFixture + public class InvalidHostTest : Test, IClassFixture where T : class, IGenericHostFixture { public InvalidHostTest(T hostFixture) { diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/MinimalValidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/MinimalValidHostTest.cs new file mode 100644 index 0000000..6f8a6ce --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/MinimalValidHostTest.cs @@ -0,0 +1,13 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting.Assets +{ + public class MinimalValidHostTest : MinimalHostTest + { + public MinimalValidHostTest(MinimalHostFixture hostFixture) : base(hostFixture) + { + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs index ea8dea5..cc58dd5 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Assets/ValidHostTest.cs @@ -5,9 +5,9 @@ namespace Codebelt.Extensions.Xunit.Hosting.Assets { - public class ValidHostTest : HostTest + public class ValidHostTest : HostTest { - public ValidHostTest(HostFixture hostFixture) : base(hostFixture) + public ValidHostTest(GenericHostFixture hostFixture) : base(hostFixture) { } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostFixtureTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/GenericHostFixtureTest.cs similarity index 75% rename from test/Codebelt.Extensions.Xunit.Hosting.Tests/HostFixtureTest.cs rename to test/Codebelt.Extensions.Xunit.Hosting.Tests/GenericHostFixtureTest.cs index d139fd2..a6aea23 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostFixtureTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/GenericHostFixtureTest.cs @@ -6,13 +6,13 @@ namespace Codebelt.Extensions.Xunit.Hosting { - public class HostFixtureTest : Test + public class GenericHostFixtureTest : Test { - private readonly HostFixture _hostFixture; + private readonly GenericHostFixture _hostFixture; - public HostFixtureTest(ITestOutputHelper output) : base(output) + public GenericHostFixtureTest(ITestOutputHelper output) : base(output) { - _hostFixture = new HostFixture(); + _hostFixture = new GenericHostFixture(); } [Fact] @@ -24,7 +24,7 @@ public void ConfigureHost_ShouldThrowArgumentNullException_WhenHostTestIsNull() [Fact] public void ConfigureHost_ShouldThrowArgumentOutOfRangeException_WhenHostTestIsNotAssignableFromHostTest() { - var invalidHostTest = new InvalidHostTest(new HostFixture()); + var invalidHostTest = new InvalidHostTest(new GenericHostFixture()); Assert.Throws(() => _hostFixture.ConfigureHost(invalidHostTest)); } @@ -36,9 +36,9 @@ public void ConfigureHost_ShouldConfigureHostSuccessfully() _hostFixture.ConfigureHost(validHostTest); Assert.NotNull(_hostFixture.Host); - Assert.NotNull(_hostFixture.ServiceProvider); + Assert.NotNull(_hostFixture.Host.Services); Assert.NotNull(_hostFixture.Configuration); - Assert.NotNull(_hostFixture.HostingEnvironment); + Assert.NotNull(_hostFixture.Environment); } [Fact] diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/GenericHostTestFactoryTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestFactoryTest.cs similarity index 78% rename from test/Codebelt.Extensions.Xunit.Hosting.Tests/GenericHostTestFactoryTest.cs rename to test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestFactoryTest.cs index a7d94f0..37fdb68 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/GenericHostTestFactoryTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestFactoryTest.cs @@ -4,9 +4,9 @@ namespace Codebelt.Extensions.Xunit.Hosting { - public class GenericHostTestFactoryTest : Test + public class HostTestFactoryTest : Test { - public GenericHostTestFactoryTest(ITestOutputHelper output) : base(output) + public HostTestFactoryTest(ITestOutputHelper output) : base(output) { } @@ -15,7 +15,7 @@ public void Create_CallerTypeShouldHaveDeclaringTypeOfMiddlewareTestFactoryTest( { Type sut1 = GetType(); string sut2 = null; - var middleware = GenericHostTestFactory.Create(Assert.NotNull, host => + var middleware = HostTestFactory.Create(Assert.NotNull, host => { host.ConfigureAppConfiguration((context, _) => { @@ -31,7 +31,7 @@ public void Create_CallerTypeShouldHaveDeclaringTypeOfMiddlewareTestFactoryTest( [Fact] public void CreateWithHostBuilderContext_ShouldHaveApplicationNameEqualToThisAssembly_WithHostBuilderContext() { - GenericHostTestFactory.CreateWithHostBuilderContext((context, services) => + HostTestFactory.CreateWithHostBuilderContext((context, services) => { Assert.NotNull(context); Assert.NotNull(context.HostingEnvironment); @@ -46,8 +46,7 @@ public void CreateWithHostBuilderContext_ShouldHaveApplicationNameEqualToThisAss TestOutput.WriteLine(context.HostingEnvironment.ApplicationName); Assert.Equal(GetType().Assembly.GetName().Name, context.HostingEnvironment.ApplicationName); }); - }, - hostFixture: null); + }); } } } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs index bba8492..76db11f 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs @@ -5,6 +5,7 @@ using Codebelt.Extensions.Xunit.Hosting.Assets; using Cuemon.Messaging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Xunit; using Xunit.Abstractions; using Xunit.Priority; @@ -12,16 +13,19 @@ namespace Codebelt.Extensions.Xunit.Hosting { [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] - public class HostTestTest : HostTest + public class HostTestTest : HostTest { + private bool _isHostRunning = false; private readonly IServiceScope _scope; private readonly Func> _correlationsFactory; private static readonly ConcurrentBag ScopedCorrelations = new(); - public HostTestTest(HostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + public HostTestTest(GenericHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) { - _scope = hostFixture.ServiceProvider.CreateScope(); + _scope = hostFixture.Host.Services.CreateScope(); _correlationsFactory = () => _scope.ServiceProvider.GetServices().ToList(); + var lifetime = hostFixture.Host.Services.GetRequiredService(); + lifetime.ApplicationStarted.Register(() => _isHostRunning = true); } [Fact, Priority(1)] @@ -51,6 +55,12 @@ public void Test_ScopedShouldBeSame() // simulate a request Assert.Equal(c1.CorrelationId, c2.CorrelationId); } + [Fact] + public void Test_ShouldHaveRunningHost() + { + Assert.True(_isHostRunning); + } + [Fact] public void Test_ShouldHaveConfigurationEntry() { @@ -60,19 +70,17 @@ public void Test_ShouldHaveConfigurationEntry() [Fact] public void Test_ShouldHaveEnvironmentOfDevelopment() { - Assert.Equal("Development", HostingEnvironment.EnvironmentName); + Assert.Equal("Development", Environment.EnvironmentName); } [Fact] public void Test_VerifyAbstractions() { - using var hostTest = GenericHostTestFactory.Create(hostFixture: null); - Assert.IsAssignableFrom(hostTest); - Assert.IsAssignableFrom(hostTest); + using var hostTest = HostTestFactory.Create(); + Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); - Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); - Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); Assert.IsAssignableFrom(hostTest); } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostFixtureTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostFixtureTest.cs new file mode 100644 index 0000000..7514f65 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostFixtureTest.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.Assets; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + public class MinimalHostFixtureTest : Test + { + private readonly MinimalHostFixture _hostFixture; + + public MinimalHostFixtureTest(ITestOutputHelper output) : base(output) + { + _hostFixture = new MinimalHostFixture(); + } + + [Fact] + public void ConfigureHost_ShouldThrowArgumentNullException_WhenHostTestIsNull() + { + Assert.Throws(() => _hostFixture.ConfigureHost(null)); + } + + [Fact] + public void ConfigureHost_ShouldThrowArgumentOutOfRangeException_WhenHostTestIsNotAssignableFromHostTest() + { + var invalidHostTest = new InvalidHostTest(new GenericHostFixture()); + Assert.Throws(() => _hostFixture.ConfigureHost(invalidHostTest)); + } + + [Fact] + public void ConfigureHost_ShouldConfigureHostSuccessfully() + { + var validHostTest = new MinimalValidHostTest(_hostFixture); + + _hostFixture.ConfigureHost(validHostTest); + + Assert.NotNull(_hostFixture.Host); + Assert.NotNull(_hostFixture.Host.Services); + Assert.NotNull(_hostFixture.Configuration); + Assert.NotNull(_hostFixture.Environment); + } + + [Fact] + public void Dispose_ShouldDisposeResources() + { + _hostFixture.Dispose(); + Assert.True(_hostFixture.Disposed); + } + + [Fact] + public async Task DisposeAsync_ShouldDisposeResourcesAsync() + { + await _hostFixture.DisposeAsync(); + Assert.True(_hostFixture.Disposed); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostTestFactoryTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostTestFactoryTest.cs new file mode 100644 index 0000000..004aed7 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostTestFactoryTest.cs @@ -0,0 +1,46 @@ +using System; +using Xunit; +using Xunit.Abstractions; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + public class MinimalHostTestFactoryTest : Test + { + public MinimalHostTestFactoryTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Create_CallerTypeShouldHaveDeclaringTypeOfMiddlewareTestFactoryTest() + { + Type sut1 = GetType(); + string sut2 = null; + var middleware = MinimalHostTestFactory.Create(Assert.NotNull, host => + { + sut2 = host.Environment.ApplicationName; + }); + + Assert.True(sut1 == middleware.CallerType.DeclaringType); + Assert.Equal(GetType().Assembly.GetName().Name, sut2); + } + + + [Fact] + public void CreateWithHostBuilderContext_ShouldHaveApplicationNameEqualToThisAssembly_WithHostBuilderContext() + { + MinimalHostTestFactory.CreateWithHostBuilderContext((context, services) => + { + Assert.NotNull(context); + Assert.NotNull(context.HostingEnvironment); + Assert.NotNull(context.Configuration); + Assert.NotNull(context.Properties); + Assert.NotNull(services); + }, + host => + { + TestOutput.WriteLine(host.Environment.ApplicationName); + Assert.Equal(GetType().Assembly.GetName().Name, host.Environment.ApplicationName); + }); + } + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostTestTest.cs new file mode 100644 index 0000000..8a19085 --- /dev/null +++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/MinimalHostTestTest.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Codebelt.Extensions.Xunit.Hosting.Assets; +using Cuemon.Messaging; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; +using Xunit.Abstractions; +using Xunit.Priority; + +namespace Codebelt.Extensions.Xunit.Hosting +{ + [TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)] + public class MinimalHostTestTest : MinimalHostTest + { + private bool _isHostRunning = false; + private readonly IServiceScope _scope; + private readonly Func> _correlationsFactory; + private static readonly ConcurrentBag ScopedCorrelations = new(); + + public MinimalHostTestTest(MinimalHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + _scope = hostFixture.Host.Services.CreateScope(); + _correlationsFactory = () => _scope.ServiceProvider.GetServices().ToList(); + var lifetime = hostFixture.Host.Services.GetRequiredService(); + lifetime.ApplicationStarted.Register(() => _isHostRunning = true); + } + + [Fact, Priority(1)] + public void Test_SingletonShouldBeSame() // simulate a request + { + ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation)); + var c1 = _correlationsFactory().Single(c => c is SingletonCorrelation); + var c2 = _correlationsFactory().Single(c => c is SingletonCorrelation); + Assert.Equal(c1.CorrelationId, c2.CorrelationId); + } + + [Fact, Priority(2)] + public void Test_TransientShouldBeDifferent() // simulate a request + { + ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation)); + var c1 = _correlationsFactory().Single(c => c is TransientCorrelation); + var c2 = _correlationsFactory().Single(c => c is TransientCorrelation); + Assert.NotEqual(c1.CorrelationId, c2.CorrelationId); + } + + [Fact, Priority(3)] + public void Test_ScopedShouldBeSame() // simulate a request + { + ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation)); + var c1 = _correlationsFactory().Single(c => c is ScopedCorrelation); + var c2 = _correlationsFactory().Single(c => c is ScopedCorrelation); + Assert.Equal(c1.CorrelationId, c2.CorrelationId); + } + + [Fact] + public void Test_ShouldHaveRunningHost() + { + Assert.True(_isHostRunning); + } + + [Fact] + public void Test_ShouldHaveConfigurationEntry() + { + Assert.Equal("xUnit", Configuration.GetSection("unitTestTool").Value); + } + + [Fact] + public void Test_ShouldHaveEnvironmentOfDevelopment() + { + Assert.Equal("Development", Environment.EnvironmentName); + } + + [Fact] + public void Test_VerifyAbstractions() + { + using var hostTest = HostTestFactory.Create(); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + Assert.IsAssignableFrom(hostTest); + } + + protected override void OnDisposeManagedResources() + { + _scope?.Dispose(); + } + + protected override void ConfigureHost(IHostApplicationBuilder hb) + { + hb.Services.AddSingleton(); + hb.Services.AddTransient(); + hb.Services.AddScoped(); + } + } +} From 6107445fb51be0a7ea275a92d4d7881472825b68 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 21:03:02 +0200 Subject: [PATCH 13/15] docs --- src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs | 2 +- src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs index 4dc5a31..2fcb209 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/HostTest.cs @@ -8,7 +8,7 @@ namespace Codebelt.Extensions.Xunit.Hosting { /// - /// Represents the non-generic base class from its generic equivalent should derive. + /// Represents the non-generic base class from where its generic equivalent should derive. /// /// /// diff --git a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs index 60c8f4a..b97e197 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs @@ -16,7 +16,7 @@ public interface IHostFixture : IConfigurationTest, IEnvironmentTest, IDisposabl /// /// Gets the initialized by either the or . /// - /// The initialized by the . + /// The initialized by the or . IHost Host { get; } /// From c0aa8747e0bed6e2c0d9b49354322606d9c0552c Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 21:03:14 +0200 Subject: [PATCH 14/15] :package: updated NuGet package definition --- .../PackageReleaseNotes.txt | 8 ++++- .../PackageReleaseNotes.txt | 24 ++++++++++++++- .../PackageReleaseNotes.txt | 30 +++++++++++++++++-- .../PackageReleaseNotes.txt | 8 ++++- 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt index 9ced0dc..851bacd 100644 --- a/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version 9.1.3 +Version 10.0.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +Version 9.1.3 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 ddb6bd8..24c7365 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt @@ -1,4 +1,26 @@ -Version 9.1.3 +Version 10.0.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +# Breaking Changes +- RENAMED AspNetCoreHostFixture class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace to WebHostFixture +- REMOVED AspNetCoreHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace +- RENAMED AspNetCoreHostTest class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace to WebHostTest +- RENAMED BlockingAspNetCoreHostFixture class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace to BlockingWebHostFixture +- RENAMED IAspNetCoreHostFixture interface in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace to IWebHostFixture +  +# New Features +- ADDED WebHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that consist of one extension method for the IWebHostFixture interface: HasValidState +- ADDED HostBuilderApplicationExtensions class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that consist of one extension method for the IHostApplicationBuilder interface: ToHostBuilder +- ADDED IMinimalWebHostFixture interface in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that provides a way to use Microsoft Dependency Injection in unit tests (minimal style) +- ADDED MinimalWebHostFixture class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that provides a default implementation of the IMinimalWebHostFixture interface +- ADDED MinimalWebHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that consist of one extension method for the IMinimalWebHostFixture interface: HasValidState +- ADDED MinimalWebHostTest{T} class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that represents a base class from which all implementations of unit testing, that uses Microsoft Dependency Injection and depends on ASP.NET Core (minimal style), should derive +- ADDED MinimalWebHostTestFactory class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace that provides a set of static methods for ASP.NET Core (minimal style) unit testing +  +Version 9.1.3 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 009a197..f654cc1 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt @@ -1,4 +1,30 @@ -Version 9.1.3 +Version 10.0.0 +Availability: .NET 9, .NET 8 and .NET Standard 2.0 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +# Breaking Changes +- RENAMED IHostingEnvironmentTest in the Codebelt.Extensions.Xunit.Hosting namespace to IEnvironmentTest +- RENAMED GenericHostTestFactory in the Codebelt.Extensions.Xunit.Hosting namespace to HostTestFactory +- RENAMED IGenericHostTest in the Codebelt.Extensions.Xunit.Hosting namespace to IHostTest +- REMOVED IServiceTest interface in the Codebelt.Extensions.Xunit.Hosting namespace due to redundancies with the IHost interface (Services property) +- CHANGED HostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace to an abstract class from which all other host fixture classes derive from (e.g., WebHostFixture, GenericHostFixture, etc.) +- REMOVED HostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace +- CHANGED IHostFixture interface in the Codebelt.Extensions.Xunit.Hosting namespace to be more confined in scope (e.g., less interface inheritance and ultimately fewer members) +  +# 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 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) +  +Version 9.1.3 Availability: .NET 9, .NET 8 and .NET Standard 2.0   # ALM @@ -33,7 +59,7 @@ Availability: .NET 9, .NET 8 and .NET Standard 2.0 - CHANGED HostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace so that IHostEnvironment.ApplicationName is aligned with the equivalent logic found in AspNetCoreHostFixture class (e.g., the assembly name of the calling Test type is used as the default value for the ApplicationName property)   # New Features -- EXTENDED LoggerExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace with one new extension method for the ILogger interface: An overload of GetTestStore that takes an optional string argument (categoryName) +- EXTENDED LoggerExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace with one new extension method for the ILogger interface: An overload of GetTestStore that takes an optional string argument (categoryName)   Version 9.1.0 Availability: .NET 9, .NET 8 and .NET Standard 2.0 diff --git a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt index 20c1c73..3a769c1 100644 --- a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt @@ -1,4 +1,10 @@ -Version 9.1.3 +Version 10.0.0 +Availability: .NET 9 and .NET 8 +  +# ALM +- CHANGED Dependencies to latest and greatest with respect to TFMs +  +Version 9.1.3 Availability: .NET 9 and .NET 8   # ALM From c95fb3c486cc8a9f08369e2b37a2376e41bb8289 Mon Sep 17 00:00:00 2001 From: Michael Mortensen Date: Mon, 7 Apr 2025 21:03:23 +0200 Subject: [PATCH 15/15] :speech_balloon: updated community health pages --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aa5727..3fd6148 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,49 @@ 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. +## [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. + +### 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 +- 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 +- 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) +- 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 +- 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 +- 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) + +### Changed + +- IHostingEnvironmentTest in the Codebelt.Extensions.Xunit.Hosting namespace was renamed to IEnvironmentTest (breaking change) +- GenericHostTestFactory in the Codebelt.Extensions.Xunit.Hosting namespace was renamed to HostTestFactory (breaking change) +- IGenericHostTest in the Codebelt.Extensions.Xunit.Hosting namespace was renamed to IHostTest (breaking change) +- HostFixture class in the Codebelt.Extensions.Xunit.Hosting namespace was changed to an abstract class from which all other host fixture classes derive from (e.g., WebHostFixture, GenericHostFixture, etc.) +- IHostFixture interface in the Codebelt.Extensions.Xunit.Hosting namespace was changed to be more confined in scope (e.g., less interface inheritance and ultimately fewer members) +- AspNetCoreHostFixture class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace was renamed to WebHostFixture (breaking change) +- AspNetCoreHostTest class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace was renamed to WebHostTest (breaking change) +- BlockingAspNetCoreHostFixture class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace was renamed to BlockingWebHostFixture (breaking change) +- IAspNetCoreHostFixture interface in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace was renamed to IWebHostFixture (breaking change) + +### Removed + +- IServiceTest interface in the Codebelt.Extensions.Xunit.Hosting namespace due to redundancies with the IHost interface (Services property) (breaking change) +- HostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace (breaking change) +- AspNetCoreHostFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace (breaking change) + ## [9.1.3] - 2025-04-03 ### Fixed