Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion Common.targets
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<Version>5.4.6</Version>
<Version>5.5.0</Version>
<LangVersion>preview</LangVersion>
<Authors>Avanade</Authors>
<Company>Avanade</Company>
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,20 @@ test.Run<Gin, int>(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<Startup>();
test.Run<Gin, int>(gin => gin.Pour())
.AssertSuccess()
.AssertValue(1);

public class Startup
{
public void ConfigureApplication(IHostApplicationBuilder builder) => builder.Services.AddSingleton<Gin>();
}
```

<br/>

## DI Mocking
Expand Down
53 changes: 52 additions & 1 deletion src/UnitTestEx/Generic/GenericTesterCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -58,7 +59,56 @@ private IHost GetHost()

var ep = new EntryPoint(Activator.CreateInstance<TEntryPoint>());

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 =>
Expand Down Expand Up @@ -88,6 +138,7 @@ private IHost GetHost()
SetUp.ConfigureServices?.Invoke(sc);
AddConfiguredServices(sc);
}).Build();
#endif
}
}

Expand Down
43 changes: 40 additions & 3 deletions src/UnitTestEx/Hosting/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

/// <summary>
/// Initializes a new instance of the <see cref="EntryPoint"/> class.
Expand All @@ -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
}

/// <summary>
/// Indicates whether the <see cref="ConfigureAppConfiguration"/> has been defined on the entry point instance.
/// </summary>
public bool HasConfigureAppConfiguration => _mi1 is not null;

/// <summary>
/// Indicates whether the <see cref="ConfigureHostConfiguration"/> has been defined on the entry point instance.
/// </summary>
public bool HasConfigureHostConfiguration => _mi2 is not null;

/// <summary>
/// Indicates whether the <see cref="ConfigureServices"/> has been defined on the entry point instance.
/// </summary>
public bool HasConfigureServices => _mi3 is not null;

#if NET8_0_OR_GREATER
/// <summary>
/// Indicates whether the <see cref="ConfigureApplication"/> has been defined on the entry point instance.
/// </summary>
public bool HasConfigureApplication => _mi4 is not null;
#endif

/// <summary>
/// Sets up the configuration for the remainder of the build process and application.
/// </summary>
/// <param name="context"></param>
/// <param name="config"></param>
/// <remarks>This is intended to be invoked by the <see cref="IHostBuilder.ConfigureAppConfiguration(System.Action{HostBuilderContext, IConfigurationBuilder})"/>.</remarks>
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]);

/// <summary>
/// Sets up the configuration for the builder itself to initialize the <see cref="IHostEnvironment"/>.
/// </summary>
/// <param name="config">The <see cref="IConfigurationBuilder"/>.</param>
/// <remarks>This is intended to be invoked by the <see cref="IHostBuilder.ConfigureHostConfiguration(System.Action{IConfigurationBuilder})"/>.</remarks>
public void ConfigureHostConfiguration(IConfigurationBuilder config) => _mi2?.Invoke(_instance, new object[] { config });
public void ConfigureHostConfiguration(IConfigurationBuilder config) => _mi2?.Invoke(_instance, [config]);

/// <summary>
/// Adds services to the container.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <remarks>This is intended to be invoked by the <see cref="IHostBuilder.ConfigureServices(System.Action{HostBuilderContext, IServiceCollection})"/>.</remarks>
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
/// <summary>
/// Enables further configuration of the <see cref="IHostApplicationBuilder"/> after it has been created/pre-configured.
/// </summary>
/// <param name="builder">The <see cref="IHostApplicationBuilder"/></param>
public void ConfigureApplication(IHostApplicationBuilder builder) => _mi4?.Invoke(_instance, [builder]);
#endif
}
}
2 changes: 1 addition & 1 deletion tests/UnitTestEx.Api/Controllers/PersonController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public IActionResult Get(int id)
[HttpGet("")]
public IActionResult GetByArgs(string firstName, string lastName, [FromQuery] List<int> id = default)
{
return new ObjectResult($"{firstName}-{lastName}-{string.Join(",", id)}");
return new ObjectResult($"{firstName}-{lastName}-{(id is null ? "" : string.Join(",", id))}");
}

[HttpPost("{id}")]
Expand Down
11 changes: 9 additions & 2 deletions tests/UnitTestEx.Api/UnitTestEx.Api.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<PreserveCompilationContext>true</PreserveCompilationContext>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.18" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.14" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.18" />
</ItemGroup>

</Project>
14 changes: 13 additions & 1 deletion tests/UnitTestEx.NUnit.Test/Other/GenericTest.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -39,7 +40,7 @@ public void Run_Exception()
[Test]
public void Run_Service()
{
using var test = GenericTester.Create().ConfigureServices(services => services.AddSingleton<Gin>());
using var test = GenericTester.Create<EntryPoint>();

test.Run<Gin, int>(gin => gin.Pour())
.AssertSuccess()
Expand Down Expand Up @@ -83,4 +84,15 @@ public void Shake() { }
public int Pour() => 1;
public Task<int> 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<Gin>();
}
}
2 changes: 1 addition & 1 deletion tests/UnitTestEx.NUnit.Test/UnitTestEx.NUnit.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion tests/UnitTestEx.Xunit.Test/UnitTestEx.Xunit.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>
Expand Down
Loading