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 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 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 @@ - + 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/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/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/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/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/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/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/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/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/AspNetCoreHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTest.cs similarity index 62% rename from src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs rename to src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTest.cs index 005c2ad..028591a 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostTest.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/MinimalWebHostTest.cs @@ -1,50 +1,55 @@ 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, should derive. + /// 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. - /// - /// - public abstract class AspNetCoreHostTest : HostTest, IWebHostTest where T : class, IAspNetCoreHostFixture + /// 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. + /// 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. - protected AspNetCoreHostTest(T hostFixture, ITestOutputHelper output = null, Type callerType = null) : this(false, hostFixture, output, callerType) + protected MinimalWebHostTest(T hostFixture, ITestOutputHelper output = null, Type callerType = null) : this(false, hostFixture, output, callerType) { } /// - /// Initializes a new instance of the class. + /// 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. - protected AspNetCoreHostTest(bool skipHostFixtureInitialization, T hostFixture, ITestOutputHelper output = null, Type callerType = null) : base(skipHostFixtureInitialization, hostFixture, output, callerType) + /// + /// 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.ConfigureHostCallback = ConfigureHost; hostFixture.ConfigureCallback = Configure; - hostFixture.ConfigureServicesCallback = ConfigureServices; + hostFixture.ConfigureHostCallback = ConfigureHost; hostFixture.ConfigureApplicationCallback = ConfigureApplication; hostFixture.ConfigureHost(this); } Host = hostFixture.Host; - ServiceProvider = hostFixture.Host.Services; Application = hostFixture.Application; - Configure(hostFixture.Configuration, hostFixture.HostingEnvironment); + Configure(hostFixture.Configuration, hostFixture.Environment); } /// 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); + } + } +} 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); 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/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/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..2fcb209 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 where 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/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/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; } } } 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/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/IHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs index 3a17b74..b97e197 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/IHostFixture.cs @@ -1,44 +1,28 @@ 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 either the or . /// - /// The delegate that adds services to the container. - Action ConfigureServicesCallback { get; set; } + /// The initialized by the or . + IHost Host { get; } /// - /// Gets or sets the delegate that provides a way to override the defaults set up by . + /// Gets or sets the delegate that adds configuration and environment information to a . /// - /// 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); - - /// - /// 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; } } } 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; } } } 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/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; } - } -} 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); } 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..b89b747 --- /dev/null +++ b/src/Codebelt.Extensions.Xunit.Hosting/MinimalHostFixture.cs @@ -0,0 +1,61 @@ +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()); + } + } +} 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(); + } + } +}