diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fdc4f4..1521e5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,14 +36,14 @@ jobs: - name: Explicit NUnit test run: | - cp tests/UnitTestEx.Api/bin/Debug/net6.0/UnitTestEx.Api.deps.json tests/UnitTestEx.NUnit.Test/bin/Debug/net6.0 - cd tests/UnitTestEx.NUnit.Test/bin/Debug/net6.0 + cp tests/UnitTestEx.Api/bin/Debug/net8.0/UnitTestEx.Api.deps.json tests/UnitTestEx.NUnit.Test/bin/Debug/net8.0 + cd tests/UnitTestEx.NUnit.Test/bin/Debug/net8.0 dotnet test UnitTestEx.NUnit.Test.dll --no-build --verbosity normal - name: Explicit Xunit test run: | - cp tests/UnitTestEx.Api/bin/Debug/net6.0/UnitTestEx.Api.deps.json tests/UnitTestEx.Xunit.Test/bin/Debug/net6.0 - cd tests/UnitTestEx.Xunit.Test/bin/Debug/net6.0 + cp tests/UnitTestEx.Api/bin/Debug/net8.0/UnitTestEx.Api.deps.json tests/UnitTestEx.Xunit.Test/bin/Debug/net8.0 + cd tests/UnitTestEx.Xunit.Test/bin/Debug/net8.0 dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests MockHttpClientTest dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests PersonFunctionTest dotnet test UnitTestEx.Xunit.Test.dll --no-build --verbosity normal --tests ProductFunctionTest diff --git a/CHANGELOG.md b/CHANGELOG.md index 511f357..f88c60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ Represents the **NuGet** versions. +## v5.5.0 +- *Enhancement:* The `GenericTester` where using `.NET8.0` and above will leverage the new `IHostApplicationBuilder` versus existing `IHostBuilder` (see Microsoft [documentation](https://learn.microsoft.com/en-us/dotnet/core/extensions/generic-host) and [recommendation](https://github.com/dotnet/runtime/discussions/81090#discussioncomment-4784551)). Additionally, if a `TEntryPoint` is specified with a method signature of `public void ConfigureApplication(IHostApplicationBuilder builder)` then this will be automatically invoked during host instantiation. This is a non-breaking change as largely internal. + ## v5.4.6 - *Fixed:* Added `TestFrameworkImplementor.SetLocalCreateFactory` to Xunit `ApiTestFixture` constructor to ensure set correctly for the `OnConfiguration` method override. diff --git a/Common.targets b/Common.targets index d2da746..f89c20a 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@  - 5.4.6 + 5.5.0 preview Avanade Avanade diff --git a/README.md b/README.md index f0f2611..04cb91a 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,20 @@ test.Run(gin => gin.Pour()) .AssertValue(1); ``` +Additionally, where the `TEntryPoint` is specified and implements `ConfigureApplication` (`.NET8.0` or above) this will be invoked automatically to perform any additional configuration. + +``` csharp +using var test = GenericTester.Create(); +test.Run(gin => gin.Pour()) + .AssertSuccess() + .AssertValue(1); + +public class Startup +{ + public void ConfigureApplication(IHostApplicationBuilder builder) => builder.Services.AddSingleton(); +} +``` +
## DI Mocking diff --git a/src/UnitTestEx/Generic/GenericTesterCore.cs b/src/UnitTestEx/Generic/GenericTesterCore.cs index 4cdc647..4f37ffc 100644 --- a/src/UnitTestEx/Generic/GenericTesterCore.cs +++ b/src/UnitTestEx/Generic/GenericTesterCore.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; +using System.Reflection; using UnitTestEx.Abstractions; using UnitTestEx.Expectations; using UnitTestEx.Hosting; @@ -58,7 +59,56 @@ private IHost GetHost() var ep = new EntryPoint(Activator.CreateInstance()); - return _host ??= new HostBuilder() +#if NET8_0_OR_GREATER + var settings = new HostApplicationBuilderSettings + { + EnvironmentName = TestSetUp.Environment, + ContentRootPath = Environment.CurrentDirectory + }; + + var builder = Host.CreateApplicationBuilder(settings); + builder.Logging.SetMinimumLevel(SetUp.MinimumLogLevel).ClearProviders().AddProvider(LoggerProvider); + + if (ep.HasConfigureHostConfiguration) + ep.ConfigureHostConfiguration(builder.Configuration); + + builder.Configuration.AddJsonFile("appsettings.json", optional: true) + .AddJsonFile($"appsettings.{TestSetUp.Environment.ToLowerInvariant()}.json", optional: true); + + if (ep.HasConfigureAppConfiguration) + { + var fi = builder.GetType().GetField("_hostBuilderContext", BindingFlags.Instance | BindingFlags.NonPublic); + if (fi is not null) + { + var hbc = (HostBuilderContext)fi.GetValue(builder)!; + ep.ConfigureAppConfiguration(hbc, builder.Configuration); + } + } + + builder.Configuration.AddJsonFile("appsettings.unittest.json", optional: true) + .AddEnvironmentVariables(); + + if (AdditionalConfiguration != null) + builder.Configuration.AddInMemoryCollection(AdditionalConfiguration); + + if (ep.HasConfigureServices) + ep.ConfigureServices(builder.Services); + + if (ep.HasConfigureApplication) + ep.ConfigureApplication(builder); + + builder.Services.ReplaceScoped(_ => SharedState); + + foreach (var tec in TestSetUp.Extensions) + tec.ConfigureServices(this, builder.Services); + + SetUp.ConfigureServices?.Invoke(builder.Services); + AddConfiguredServices(builder.Services); + + _host = builder.Build(); + return _host; +#else + return _host ??= Host.CreateDefaultBuilder() .UseEnvironment(TestSetUp.Environment) .ConfigureLogging((lb) => { lb.SetMinimumLevel(SetUp.MinimumLogLevel); lb.ClearProviders(); lb.AddProvider(LoggerProvider); }) .ConfigureHostConfiguration(cb => @@ -88,6 +138,7 @@ private IHost GetHost() SetUp.ConfigureServices?.Invoke(sc); AddConfiguredServices(sc); }).Build(); +#endif } } diff --git a/src/UnitTestEx/Hosting/EntryPoint.cs b/src/UnitTestEx/Hosting/EntryPoint.cs index 86f16e2..34ce68c 100644 --- a/src/UnitTestEx/Hosting/EntryPoint.cs +++ b/src/UnitTestEx/Hosting/EntryPoint.cs @@ -18,6 +18,9 @@ public class EntryPoint private readonly MethodInfo? _mi1; private readonly MethodInfo? _mi2; private readonly MethodInfo? _mi3; +#if NET8_0_OR_GREATER + private readonly MethodInfo? _mi4; +#endif /// /// Initializes a new instance of the class. @@ -29,28 +32,62 @@ public EntryPoint(object instance) _mi1 = instance.GetType().GetMethod(nameof(ConfigureAppConfiguration), BindingFlags.Instance | BindingFlags.Public, [typeof(HostBuilderContext), typeof(IConfigurationBuilder)]); _mi2 = instance.GetType().GetMethod(nameof(ConfigureHostConfiguration), BindingFlags.Instance | BindingFlags.Public, [typeof(IConfigurationBuilder)]); _mi3 = instance.GetType().GetMethod(nameof(ConfigureServices), BindingFlags.Instance | BindingFlags.Public, [typeof(IServiceCollection)]); + _mi3 = instance.GetType().GetMethod(nameof(ConfigureServices), BindingFlags.Instance | BindingFlags.Public, [typeof(IServiceCollection)]); +#if NET8_0_OR_GREATER + _mi4 = instance.GetType().GetMethod(nameof(ConfigureApplication), BindingFlags.Instance | BindingFlags.Public, [typeof(IHostApplicationBuilder)]); +#endif } + /// + /// Indicates whether the has been defined on the entry point instance. + /// + public bool HasConfigureAppConfiguration => _mi1 is not null; + + /// + /// Indicates whether the has been defined on the entry point instance. + /// + public bool HasConfigureHostConfiguration => _mi2 is not null; + + /// + /// Indicates whether the has been defined on the entry point instance. + /// + public bool HasConfigureServices => _mi3 is not null; + +#if NET8_0_OR_GREATER + /// + /// Indicates whether the has been defined on the entry point instance. + /// + public bool HasConfigureApplication => _mi4 is not null; +#endif + /// /// Sets up the configuration for the remainder of the build process and application. /// /// /// /// This is intended to be invoked by the . - public void ConfigureAppConfiguration(HostBuilderContext context, IConfigurationBuilder config) => _mi1?.Invoke(_instance, new object[] { context, config }); + public void ConfigureAppConfiguration(HostBuilderContext context, IConfigurationBuilder config) => _mi1?.Invoke(_instance, [context, config]); /// /// Sets up the configuration for the builder itself to initialize the . /// /// The . /// This is intended to be invoked by the . - public void ConfigureHostConfiguration(IConfigurationBuilder config) => _mi2?.Invoke(_instance, new object[] { config }); + public void ConfigureHostConfiguration(IConfigurationBuilder config) => _mi2?.Invoke(_instance, [config]); /// /// Adds services to the container. /// /// The . /// This is intended to be invoked by the . - public void ConfigureServices(IServiceCollection services) => _mi3?.Invoke(_instance, new object[] { services }); + public void ConfigureServices(IServiceCollection services) => _mi3?.Invoke(_instance, [services]); + +#if NET8_0_OR_GREATER + /// + /// Enables further configuration of the after it has been created/pre-configured. + /// + /// The + public void ConfigureApplication(IHostApplicationBuilder builder) => _mi4?.Invoke(_instance, [builder]); +#endif } } \ No newline at end of file diff --git a/tests/UnitTestEx.Api/Controllers/PersonController.cs b/tests/UnitTestEx.Api/Controllers/PersonController.cs index d42b51e..a4d8fa5 100644 --- a/tests/UnitTestEx.Api/Controllers/PersonController.cs +++ b/tests/UnitTestEx.Api/Controllers/PersonController.cs @@ -44,7 +44,7 @@ public IActionResult Get(int id) [HttpGet("")] public IActionResult GetByArgs(string firstName, string lastName, [FromQuery] List id = default) { - return new ObjectResult($"{firstName}-{lastName}-{string.Join(",", id)}"); + return new ObjectResult($"{firstName}-{lastName}-{(id is null ? "" : string.Join(",", id))}"); } [HttpPost("{id}")] diff --git a/tests/UnitTestEx.Api/UnitTestEx.Api.csproj b/tests/UnitTestEx.Api/UnitTestEx.Api.csproj index 0e5f699..aca444e 100644 --- a/tests/UnitTestEx.Api/UnitTestEx.Api.csproj +++ b/tests/UnitTestEx.Api/UnitTestEx.Api.csproj @@ -1,14 +1,21 @@  - net6.0 + net6.0;net8.0 true latest - + + + + + + + + diff --git a/tests/UnitTestEx.NUnit.Test/Other/GenericTest.cs b/tests/UnitTestEx.NUnit.Test/Other/GenericTest.cs index 749565f..890b70c 100644 --- a/tests/UnitTestEx.NUnit.Test/Other/GenericTest.cs +++ b/tests/UnitTestEx.NUnit.Test/Other/GenericTest.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using NUnit.Framework; using System; using System.Threading.Tasks; @@ -39,7 +40,7 @@ public void Run_Exception() [Test] public void Run_Service() { - using var test = GenericTester.Create().ConfigureServices(services => services.AddSingleton()); + using var test = GenericTester.Create(); test.Run(gin => gin.Pour()) .AssertSuccess() @@ -83,4 +84,15 @@ public void Shake() { } public int Pour() => 1; public Task PourAsync() => Task.FromResult(1); } + + public class EntryPoint + { + //public void ConfigureAppConfiguration(HostBuilderContext context, IConfigurationBuilder config) { } + + //public void ConfigureHostConfiguration(IConfigurationBuilder config) { } + + //public void ConfigureServices(IServiceCollection services) { } + + public void ConfigureApplication(IHostApplicationBuilder builder) => builder.Services.AddSingleton(); + } } \ No newline at end of file diff --git a/tests/UnitTestEx.NUnit.Test/UnitTestEx.NUnit.Test.csproj b/tests/UnitTestEx.NUnit.Test/UnitTestEx.NUnit.Test.csproj index dd8a2d6..914a231 100644 --- a/tests/UnitTestEx.NUnit.Test/UnitTestEx.NUnit.Test.csproj +++ b/tests/UnitTestEx.NUnit.Test/UnitTestEx.NUnit.Test.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false preview diff --git a/tests/UnitTestEx.Xunit.Test/UnitTestEx.Xunit.Test.csproj b/tests/UnitTestEx.Xunit.Test/UnitTestEx.Xunit.Test.csproj index a255700..17c30c9 100644 --- a/tests/UnitTestEx.Xunit.Test/UnitTestEx.Xunit.Test.csproj +++ b/tests/UnitTestEx.Xunit.Test/UnitTestEx.Xunit.Test.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false