diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs
index 6bae8cb..3e7c912 100644
--- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs
+++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/AspNetCoreHostFixture.cs
@@ -80,11 +80,13 @@ public override void ConfigureHost(Test hostTest)
.UseSetting(HostDefaults.ApplicationKey, hostTest.CallerType.Assembly.GetName().Name);
});
+#if NET9_0_OR_GREATER
hb.UseDefaultServiceProvider(o =>
{
o.ValidateOnBuild = true;
o.ValidateScopes = true;
});
+#endif
ConfigureHostCallback(hb);
diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Http/FakeHttpContextAccessor.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Http/FakeHttpContextAccessor.cs
index 2304910..9ca0c80 100644
--- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Http/FakeHttpContextAccessor.cs
+++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/Http/FakeHttpContextAccessor.cs
@@ -4,6 +4,7 @@
using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http
{
@@ -13,17 +14,21 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http
///
public class FakeHttpContextAccessor : IHttpContextAccessor
{
-
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
- public FakeHttpContextAccessor()
+ /// An optional for resolving services.
+ public FakeHttpContextAccessor(IServiceScopeFactory factory = null)
{
+ var context = new DefaultHttpContext();
var fc = new FeatureCollection();
+ fc.Set(new RequestServicesFeature(context, factory));
fc.Set(new FakeHttpResponseFeature());
fc.Set(new FakeHttpRequestFeature());
fc.Set(new StreamResponseBodyFeature(MakeGreeting("Hello awesome developers!")));
- HttpContext = new DefaultHttpContext(fc);
+ context.Uninitialize();
+ context.Initialize(fc);
+ HttpContext = context;
}
private Stream MakeGreeting(string greeting)
diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/ServiceCollectionExtensions.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/ServiceCollectionExtensions.cs
index 859d0d3..c8b6978 100644
--- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/ServiceCollectionExtensions.cs
+++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/ServiceCollectionExtensions.cs
@@ -16,9 +16,9 @@ public static class ServiceCollectionExtensions
/// Adds a unit test optimized implementation for the service.
///
/// The to extend.
- /// The lifetime of the service.
+ /// The lifetime of the service. Default is .
/// A reference to after the operation has completed.
- public static IServiceCollection AddFakeHttpContextAccessor(this IServiceCollection services, ServiceLifetime lifetime)
+ public static IServiceCollection AddFakeHttpContextAccessor(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
switch (lifetime)
{
@@ -39,8 +39,7 @@ public static IServiceCollection AddFakeHttpContextAccessor(this IServiceCollect
private static IHttpContextAccessor FakeHttpContextAccessorFactory(IServiceProvider provider)
{
- var contextAccessor = new FakeHttpContextAccessor { HttpContext = { RequestServices = provider } };
- return contextAccessor;
+ return new FakeHttpContextAccessor(provider.GetRequiredService());
}
}
}
diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs
index 0d722bf..b31c050 100644
--- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs
+++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebHostTest.cs
@@ -83,7 +83,7 @@ public override void ConfigureServices(IServiceCollection services)
hbc.HostingEnvironment = HostingEnvironment;
return hbc;
}), services);
- services.AddFakeHttpContextAccessor(ServiceLifetime.Singleton);
+ services.AddFakeHttpContextAccessor();
}
}
}
diff --git a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs
index 7b6ac24..af3485a 100644
--- a/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs
+++ b/src/Codebelt.Extensions.Xunit.Hosting/HostFixture.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -12,6 +13,8 @@ namespace Codebelt.Extensions.Xunit.Hosting
///
public class HostFixture : IDisposable, IHostFixture
{
+ private readonly object _lock = new();
+
///
/// Initializes a new instance of the class.
///
@@ -54,13 +57,22 @@ public virtual void ConfigureHost(Test hostTest)
ConfigureServicesCallback(services);
});
- ConfigureHostCallback(hb);
-
#if NET9_0_OR_GREATER
- hb.UseDefaultServiceProvider(o => o.ValidateScopes = false); // this is by intent
+ hb.UseDefaultServiceProvider(o =>
+ {
+ o.ValidateOnBuild = true;
+ o.ValidateScopes = true;
+ });
#endif
- Host = hb.Build();
+ ConfigureHostCallback(hb);
+
+ var host = hb.Build();
+ Task.Run(() => host.StartAsync().ConfigureAwait(false))
+ .ConfigureAwait(false)
+ .GetAwaiter()
+ .GetResult();
+ Host = host;
}
///
@@ -185,12 +197,16 @@ public void Dispose()
protected void Dispose(bool disposing)
{
if (Disposed) { return; }
- if (disposing)
+ lock (_lock)
{
- OnDisposeManagedResources();
+ if (Disposed) { return; }
+ if (disposing)
+ {
+ OnDisposeManagedResources();
+ }
+ OnDisposeUnmanagedResources();
+ Disposed = true;
}
- OnDisposeUnmanagedResources();
- Disposed = true;
}
}
}
diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs
index 559e90a..07fb254 100644
--- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs
+++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/AspNetCoreHostTestTest.cs
@@ -2,8 +2,8 @@
using System.Linq;
using System.Threading.Tasks;
using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets;
-using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http;
using Cuemon.Extensions.IO;
+using Cuemon.Messaging;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
@@ -35,8 +35,8 @@ public async Task ShouldHaveResultOfBoolMiddlewareInBody()
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));
+ var logger = _pipeline.ApplicationServices.GetRequiredService>();
+ logger.LogInformation("Hello from {0}", nameof(ShouldHaveResultOfBoolMiddlewareInBody));
await pipeline(context);
@@ -50,14 +50,77 @@ public async Task ShouldHaveResultOfBoolMiddlewareInBody()
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 from {0}", nameof(ShouldLogToXunitTestLogging));
var store = _pipeline.ApplicationServices.GetRequiredService>().GetTestStore();
var entry = store.Query(entry => entry.Message.Contains("Hello from", StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
-
+
Assert.NotNull(entry);
Assert.Equal("Information: Hello from ShouldLogToXunitTestLogging", entry.Message);
}
@@ -70,7 +133,7 @@ public override void ConfigureApplication(IApplicationBuilder app)
public override void ConfigureServices(IServiceCollection services)
{
- services.AddTransient();
+ services.AddFakeHttpContextAccessor();
services.Configure(o =>
{
o.A = true;
@@ -79,6 +142,10 @@ public override void ConfigureServices(IServiceCollection services)
});
services.AddXunitTestLoggingOutputHelperAccessor();
services.AddXunitTestLogging(TestOutput);
+
+ services.AddSingleton();
+ services.AddTransient();
+ services.AddScoped();
}
}
}
diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ScopedCorrelation.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ScopedCorrelation.cs
new file mode 100644
index 0000000..fca7bea
--- /dev/null
+++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/ScopedCorrelation.cs
@@ -0,0 +1,8 @@
+using Cuemon.Messaging;
+
+namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets
+{
+ public sealed record ScopedCorrelation : CorrelationToken
+ {
+ }
+}
diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/SingletonCorrelation.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/SingletonCorrelation.cs
new file mode 100644
index 0000000..91c264f
--- /dev/null
+++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/SingletonCorrelation.cs
@@ -0,0 +1,8 @@
+using Cuemon.Messaging;
+
+namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets
+{
+ public sealed record SingletonCorrelation : CorrelationToken
+ {
+ }
+}
diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/TransientCorrelation.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/TransientCorrelation.cs
new file mode 100644
index 0000000..1656baa
--- /dev/null
+++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Assets/TransientCorrelation.cs
@@ -0,0 +1,8 @@
+using Cuemon.Messaging;
+
+namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore.Assets
+{
+ public sealed record TransientCorrelation : CorrelationToken
+ {
+ }
+}
\ No newline at end of file
diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj
index c67c1ea..7f9809d 100644
--- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj
+++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Tests.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj
index cae2ea1..6c49c08 100644
--- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj
+++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/Codebelt.Extensions.Xunit.Hosting.Tests.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs
index 04b8533..5f89a32 100644
--- a/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs
+++ b/test/Codebelt.Extensions.Xunit.Hosting.Tests/HostTestTest.cs
@@ -14,16 +14,18 @@ namespace Codebelt.Extensions.Xunit.Hosting
[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
public class HostTestTest : HostTest
{
+ private readonly IServiceScope _scope;
private readonly Func> _correlationsFactory;
- private static readonly ConcurrentBag ScopedCorrelations = new ConcurrentBag();
+ private static readonly ConcurrentBag ScopedCorrelations = new();
public HostTestTest(HostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output)
{
- _correlationsFactory = () => hostFixture.ServiceProvider.GetServices().ToList();
+ _scope = hostFixture.ServiceProvider.CreateScope();
+ _correlationsFactory = () => _scope.ServiceProvider.GetServices().ToList();
}
[Fact, Priority(1)]
- public void Test_SingletonShouldBeSame()
+ public void Test_SingletonShouldBeSame() // simulate a request
{
ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation));
var c1 = _correlationsFactory().Single(c => c is SingletonCorrelation);
@@ -32,7 +34,7 @@ public void Test_SingletonShouldBeSame()
}
[Fact, Priority(2)]
- public void Test_TransientShouldBeDifferent()
+ public void Test_TransientShouldBeDifferent() // simulate a request
{
ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation));
var c1 = _correlationsFactory().Single(c => c is TransientCorrelation);
@@ -41,7 +43,7 @@ public void Test_TransientShouldBeDifferent()
}
[Fact, Priority(3)]
- public void Test_ScopedShouldBeSame()
+ public void Test_ScopedShouldBeSame() // simulate a request
{
ScopedCorrelations.Add(_correlationsFactory().Single(c => c is ScopedCorrelation));
var c1 = _correlationsFactory().Single(c => c is ScopedCorrelation);
@@ -49,15 +51,6 @@ public void Test_ScopedShouldBeSame()
Assert.Equal(c1.CorrelationId, c2.CorrelationId);
}
- [Fact]
- public void Test_ScopedShouldBeSameInLastTestRun()
- {
- var c1 = _correlationsFactory().Single(c => c is ScopedCorrelation);
- if (ScopedCorrelations.IsEmpty) { return; }
- Assert.Equal(3, ScopedCorrelations.Count);
- Assert.All(ScopedCorrelations, c => Assert.Equal(c1.CorrelationId, c.CorrelationId));
- }
-
[Fact]
public void Test_ShouldHaveConfigurationEntry()
{
@@ -70,6 +63,11 @@ public void Test_ShouldHaveEnvironmentOfDevelopment()
Assert.Equal("Development", HostingEnvironment.EnvironmentName);
}
+ protected override void OnDisposeManagedResources()
+ {
+ _scope?.Dispose();
+ }
+
public override void ConfigureServices(IServiceCollection services)
{
services.AddSingleton();
@@ -77,4 +75,4 @@ public override void ConfigureServices(IServiceCollection services)
services.AddScoped();
}
}
-}
\ No newline at end of file
+}