diff --git a/.docfx/Dockerfile.docfx b/.docfx/Dockerfile.docfx index aa53eba..345674a 100644 --- a/.docfx/Dockerfile.docfx +++ b/.docfx/Dockerfile.docfx @@ -1,4 +1,4 @@ -ARG NGINX_VERSION=1.31.0-alpine +ARG NGINX_VERSION=1.31.2-alpine FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base RUN rm -rf /usr/share/nginx/html/* diff --git a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.md b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.md index 435f8bb..1dab0af 100644 --- a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.md +++ b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.md @@ -2,8 +2,11 @@ uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features summary: *content --- -The `Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features` namespace contains types that provides a uniform way of doing unit testing that depends on ASP.NET Core and used in conjunction with Microsoft Dependency Injection. The namespace relates to the `Microsoft.AspNetCore.Http.Features` namespace. + +Simulate the ASP.NET Core HTTP request and response feature surfaces in unit tests without running a real server. The `Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features` namespace provides `FakeHttpRequestFeature` and `FakeHttpResponseFeature`, which implement `IHttpRequestFeature` and `IHttpResponseFeature` with settable properties backed by default values. Use these fakes when your test code reads request headers, the request body, the response status code, or response headers from the `IFeatureCollection` on `HttpContext.Features`. + +Start with `FakeHttpRequestFeature` when you need to control the HTTP method, path, query string, or request body in isolation. Use `FakeHttpResponseFeature` when the code under test writes status codes, response headers, or the response body. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] -Complements: [Microsoft.AspNetCore.Http.Features namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features) 🔗 +Complements: [Microsoft.AspNetCore.Http.Features namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.features) 🔗 \ No newline at end of file diff --git a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.md b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.md index ab6fa1d..b31d29a 100644 --- a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.md +++ b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.md @@ -2,8 +2,9 @@ uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http summary: *content --- -The `Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http` namespace contains types that provides a uniform way of doing unit testing that depends on ASP.NET Core and used in conjunction with Microsoft Dependency Injection. The namespace relates to the `Microsoft.AspNetCore.Http` namespace. + +Isolate unit tests from the real `IHttpContextAccessor` and `HttpContext` pipeline. The `Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http` namespace provides `FakeHttpContextAccessor`, a test double that implements `IHttpContextAccessor` with settable `HttpContext` and `HttpContextFactory` properties, letting you simulate request context without hosting a full server. Use this type when your tested code depends on `IHttpContextAccessor` and you need deterministic, fast test setup. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] -Complements: [Microsoft.AspNetCore.Http namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http) 🔗 +Complements: [Microsoft.AspNetCore.Http namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http) 🔗 \ No newline at end of file diff --git a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.md b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.md index 7327a8e..58a1024 100644 --- a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.md +++ b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.md @@ -2,29 +2,58 @@ uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore summary: *content --- -The `Codebelt.Extensions.Xunit.Hosting.AspNetCore` namespace contains types that provides a uniform way of doing unit testing that depends on ASP.NET Core and used in conjunction with Microsoft Dependency Injection. The namespace relates to the `Microsoft.AspNetCore.TestHost` namespace. + +Exercise an ASP.NET Core application's real entry point, dependency-injection graph, middleware pipeline, and endpoints through an in-memory `TestServer`. The `Codebelt.Extensions.Xunit.Hosting.AspNetCore` namespace can bootstrap modern minimal hosting and conventional `Startup` applications, apply test-only web-host configuration, and return either an owned test context or a reusable xUnit fixture. + +For a focused endpoint or service-override test, start with `WebApplicationTestFactory.Create` or its one-request `RunAsync` convenience. Use `WebApplicationTest` with `BlockingManagedWebApplicationFixture` when several tests should share the bootstrapped application. Reach for `WebHostTestFactory` or `MinimalWebHostTestFactory` when the test defines its own pipeline instead of loading an existing application. [!INCLUDE [availability-modern](../../includes/availability-modern.md)] -Complements: [Microsoft.AspNetCore.TestHost namespace](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.testhost) 🔗 - -### Fixture Naming Convention - -ASP.NET Core host fixtures follow the same lifecycle naming convention as the hosting package: - -|Prefix|Convention| -|---|---| -|`Managed`|The fixture owns host creation, configuration, startup and disposal using the default host runner.| -|`SelfManaged`|The fixture owns host creation and configuration, but leaves host startup to the test.| -|`BlockingManaged`|The fixture owns the host lifecycle and starts the host synchronously before returning control to the test.| - -Application-entry-point fixtures use the `BlockingManaged` prefix by default. ASP.NET Core application tests expose a `TestServer`, and callers should receive a started server after fixture initialization. Use `BlockingManagedWebApplicationFixture` when testing an existing ASP.NET Core application entry point with `TestServer`. - -`BlockingManagedWebHostFixture` remains the opt-in blocking variant for the lower-level web host fixture family. The application-entry-point fixture is named `BlockingManagedWebApplicationFixture` directly because this API is blocking by convention from its first release. - -### Extension Methods - -|Type|Ext|Methods| -|--:|:-:|---| +Complements: [ASP.NET Core integration tests](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-10.0) · [WebApplicationFactory](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1?view=aspnetcore-10.0) · [Microsoft.AspNetCore.TestHost namespace](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.testhost) 🔗 + +### Choose an ASP.NET Core Testing Path + +|When you need to|Start with|Why| +|---|---|---| +|Bootstrap an existing ASP.NET Core application for one focused test|`WebApplicationTestFactory.Create`|Returns an owned `IHostTest` whose host exposes the application's `TestServer`, services, configuration, and environment.| +|Send one request to an existing application|`WebApplicationTestFactory.RunAsync`|Combines application startup, `HttpClient` creation, request execution, and cleanup in one call.| +|Share an existing application across an xUnit test class|`WebApplicationTest` with `BlockingManagedWebApplicationFixture`|Uses xUnit fixture lifetime while keeping the real application entry point and `TestServer`.| +|Define services and middleware entirely inside the test|`WebHostTestFactory` or `MinimalWebHostTestFactory`|Builds a purpose-specific in-memory pipeline without loading an application project.| +|Attach observers or change state before startup|A `SelfManaged` web fixture|Builds the host and pipeline but leaves startup to the test.| + +### Compared with WebApplicationFactory + +`WebApplicationTestFactory` is an alternative integration-test entry point, not a drop-in replacement for `Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`. + +|Concern|`WebApplicationTestFactory`|Microsoft `WebApplicationFactory`| +|---|---|---| +|Acquisition|Static `Create` or `RunAsync` returns a Codebelt test context.|Instantiate or inject a factory, then call `CreateClient`.| +|Customization|Pass an `IWebHostBuilder` callback at the call site.|Subclass and override `ConfigureWebHost`, or compose with `WithWebHostBuilder`.| +|Sharing|Use Codebelt's `WebApplicationTest` and fixture types when tests should share a context.|Commonly shared directly through xUnit `IClassFixture>`.| +|Lifecycle|The caller disposes the returned context, or delegates ownership to a Codebelt fixture.|The factory owns its `TestServer` and clients and is disposed by the caller or xUnit fixture lifecycle.| +|Scope|Matches Codebelt's equivalent entry-point pattern for console, worker, and Generic Host applications.|Purpose-built for ASP.NET Core applications and includes MVC-testing conventions such as client options and content-root discovery.| + +### Fixture Naming Convention + +ASP.NET Core host fixtures follow the same lifecycle naming convention as the hosting package: + +|Prefix|Convention| +|---|---| +|`Managed`|The fixture owns host creation, configuration, startup and disposal using the default host runner.| +|`SelfManaged`|The fixture owns host creation and configuration, but leaves host startup to the test.| +|`BlockingManaged`|The fixture owns the host lifecycle and starts the host synchronously before returning control to the test.| + +Application-entry-point fixtures use the `BlockingManaged` prefix by default. ASP.NET Core application tests expose a `TestServer`, and callers receive a started server after fixture initialization. Use `BlockingManagedWebApplicationFixture` when testing an existing ASP.NET Core application entry point with `TestServer`. + +`BlockingManagedWebHostFixture` remains the opt-in blocking variant for the lower-level web host fixture family. The application-entry-point fixture is named `BlockingManagedWebApplicationFixture` directly because this API is blocking by convention from its first release. + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| |HttpClient|⬇️|`ToHttpResponseMessageAsync`| +|IHostApplicationBuilder|⬇️|`ToHostBuilder`| |IServiceCollection|⬇️|`AddFakeHttpContextAccessor`| +|IWebApplicationFixture<TEntryPoint>|⬇️|`HasValidState`| +|IWebHostFixture|⬇️|`HasValidState`| +|IWebMinimalHostFixture|⬇️|`HasValidState`| diff --git a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md index a588471..2261b33 100644 --- a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md +++ b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md @@ -2,28 +2,45 @@ uid: Codebelt.Extensions.Xunit.Hosting summary: *content --- -The `Codebelt.Extensions.Xunit.Hosting` namespace contains types that provides a uniform way of doing unit testing that is used in conjunction with Microsoft Dependency Injection. The namespace relates to the `Xunit.Abstractions` namespace. + +Exercise a real .NET application entry point, hosted service, or dependency-injection graph without rebuilding its host setup inside the test project. The `Codebelt.Extensions.Xunit.Hosting` namespace applies the same test model to console apps, workers, and Generic Host applications: bootstrap the application's `Program` assembly, customize the host for the scenario, inspect configuration and services, and dispose the test host through one `IHostTest` abstraction. + +For a focused test, start with `ApplicationTestFactory.Create`. It is the non-web counterpart to the entry-point pattern commonly associated with ASP.NET Core's `WebApplicationFactory`: the generic argument identifies the application assembly, while the returned test context exposes the resulting host, configuration, and environment. Use the fixture/base-class form when several xUnit tests should share that application context. [!INCLUDE [availability-default](../../includes/availability-default.md)] -Complements: [xUnit: Shared Context between Tests](https://xunit.net/docs/shared-context) 🔗 - -### Fixture Naming Convention - -Host fixtures follow a lifecycle naming convention: - -|Prefix|Convention| -|---|---| -|`Managed`|The fixture owns host creation, configuration, startup and disposal using the default host runner.| -|`SelfManaged`|The fixture owns host creation and configuration, but leaves host startup to the test.| -|`BlockingManaged`|The fixture owns the host lifecycle and starts the host synchronously before returning control to the test.| - -Application-entry-point fixtures use the `BlockingManaged` prefix by default. Existing application entry points are discovered and built from their `Program` assembly, so tests should receive a ready host after fixture initialization. Use `BlockingManagedApplicationFixture` when testing console, worker or generic host applications from an existing entry point. - -### Extension Methods - -|Type|Ext|Methods| -|--:|:-:|---| -|ILogger{T}|⬇️|`GetTestStore`| -|IServiceCollection|⬇️|`AddXunitTestOutputHelperAccessor`| -|IServiceProvider|⬇️|`GetRequiredScopedService`| +Complements: [xUnit: Shared Context between Tests](https://xunit.net/docs/shared-context) 🔗 + +### Choose a Hosting Path + +|When you need to|Start with|Why| +|---|---|---| +|Bootstrap an existing console, worker, or Generic Host application for one test|`ApplicationTestFactory.Create`|Runs the application's entry-point setup and returns an owned `IHostTest` context that the caller disposes.| +|Share an existing application across an xUnit test class|`ApplicationTest` with `BlockingManagedApplicationFixture`|Moves application startup and disposal into xUnit's fixture lifecycle while retaining configuration and service access.| +|Build a conventional Generic Host entirely inside the test|`HostTestFactory`|Configures `IServiceCollection` and `IHostBuilder` directly without requiring an application entry point.| +|Build with the modern `IHostApplicationBuilder` model|`MinimalHostTestFactory`|Keeps minimal-host tests focused on services and application-builder configuration.| +|Configure the host now but decide when it starts|A `SelfManaged` fixture|Leaves startup under test control so observers and pre-start assertions can be attached first.| + +### Fixture Naming Convention + +Host fixtures follow a lifecycle naming convention: + +|Prefix|Convention| +|---|---| +|`Managed`|The fixture owns host creation, configuration, startup and disposal using the default host runner.| +|`SelfManaged`|The fixture owns host creation and configuration, but leaves host startup to the test.| +|`BlockingManaged`|The fixture owns the host lifecycle and starts the host synchronously before returning control to the test.| + +Application-entry-point fixtures use the `BlockingManaged` prefix by default. Existing application entry points are discovered and built from their `Program` assembly, so tests receive a ready host after fixture initialization. Use `BlockingManagedApplicationFixture` when testing a console, worker, or Generic Host application from an existing entry point. + +### Extension Members + +|Type|Ext|Methods| +|--:|:-:|---| +|IApplicationFixture<TEntryPoint>|⬇️|`HasValidState`| +|IGenericHostFixture|⬇️|`HasValidState`| +|ILogger|⬇️|`GetTestStore`| +|ILogger<T>|⬇️|`GetTestStore`| +|IMinimalHostFixture|⬇️|`HasValidState`| +|IServiceCollection|⬇️|`AddXunitTestLogging` · `AddXunitTestLoggingOutputHelperAccessor` · `AddXunitTestLoggingOutputHelperAccessor`| +|IServiceProvider|⬇️|`GetRequiredScopedService`| diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationFixtureExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationFixtureExtensions.md new file mode 100644 index 0000000..4fcbc29 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationFixtureExtensions.md @@ -0,0 +1,21 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.ApplicationFixtureExtensions +example: +- *content +--- + +The following example validates that an `IApplicationFixture` has been fully initialized with host, configure callback, and configure-host callback by calling `HasValidState` on the fixture. This is useful in assertion helpers to confirm the fixture was set up correctly before running the test scenario. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; + +namespace HostFixtureTests; + +public class HostStateGuard +{ + public bool EnsureFixtureIsReady(IApplicationFixture fixture) where TEntryPoint : class + { + return fixture.HasValidState(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationHostFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationHostFactory.md new file mode 100644 index 0000000..c4e9608 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationHostFactory.md @@ -0,0 +1,48 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.ApplicationHostFactory +example: +- *content +--- + +The test project references a worker application's entry-point assembly. `ApplicationHostFactory` captures the host built by that entry point and applies a test-only service override; because this lower-level factory returns the host directly, the caller starts, stops, and disposes it explicitly. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace WorkerApp.Tests; + +public sealed class ApplicationHostFactoryExample +{ + public async Task StartWithTestIdentityAsync() + { + using IHost host = ApplicationHostFactory.Create(builder => + { + builder.ConfigureServices(services => + services.AddSingleton(new WorkerIdentity("Test inventory worker"))); + }); + + await host.StartAsync().ConfigureAwait(false); + var identity = host.Services.GetRequiredService(); + await host.StopAsync().ConfigureAwait(false); + + return identity.Name; + } +} + +public sealed record WorkerIdentity(string Name); + +public sealed class WorkerProgram +{ + public static void Main(string[] args) + { + var builder = Host.CreateApplicationBuilder(args); + builder.Services.AddSingleton(new WorkerIdentity("Inventory worker")); + + using var host = builder.Build(); + host.Run(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationTestFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationTestFactory.md new file mode 100644 index 0000000..01cf124 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationTestFactory.md @@ -0,0 +1,44 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.ApplicationTestFactory +example: +- *content +--- + +The test project references a worker application whose entry point registers `WorkerIdentity`. `ApplicationTestFactory` runs that application's real host setup, then exposes its services and environment through an owned test context so the test can verify application behavior without recreating `Program` configuration. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace WorkerApp.Tests; + +public sealed class ApplicationTestFactoryExample +{ + [Fact] + public void Create_BootstrapsWorkerApplicationServices() + { + using var application = ApplicationTestFactory.Create(); + + var identity = application.Host.Services.GetRequiredService(); + + Assert.Equal("Inventory worker", identity.Name); + Assert.Equal(Environments.Development, application.Environment.EnvironmentName); + } +} + +public sealed record WorkerIdentity(string Name); + +public sealed class WorkerProgram +{ + public static void Main(string[] args) + { + var builder = Host.CreateApplicationBuilder(args); + builder.Services.AddSingleton(new WorkerIdentity("Inventory worker")); + + using var host = builder.Build(); + host.Run(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebApplicationFixture%601.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebApplicationFixture%601.md new file mode 100644 index 0000000..7f149c3 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebApplicationFixture%601.md @@ -0,0 +1,52 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebApplicationFixture`1 +example: +- *content +--- + +The test project references a minimal ASP.NET Core application and shares its in-memory server through xUnit's class-fixture lifetime. `BlockingManagedWebApplicationFixture` waits for startup before constructing the test class, so the test can create a client from `TestServer` and exercise the real request pipeline immediately. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace CatalogApi.Tests; + +public sealed class CatalogApiTest : IClassFixture> +{ + private readonly BlockingManagedWebApplicationFixture _fixture; + + public CatalogApiTest(BlockingManagedWebApplicationFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task HealthEndpoint_ReturnsApplicationState() + { + using var client = _fixture.Server.CreateClient(); + + var body = await client.GetStringAsync("/health").ConfigureAwait(false); + + Assert.Equal("ready", body); + } +} + +public sealed record CatalogStatus(string Value); + +public sealed class CatalogProgram +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddSingleton(new CatalogStatus("ready")); + + var app = builder.Build(); + app.MapGet("/health", (CatalogStatus status) => status.Value); + app.Run(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebHostFixture.md new file mode 100644 index 0000000..42b28db --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebHostFixture.md @@ -0,0 +1,31 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebHostFixture +example: +- *content +--- + +The following example uses `BlockingManagedWebHostFixture` as a fixture for xUnit tests that need a synchronously started web host. The fixture manages the full web host lifecycle and blocks until the host is started before returning control to the test. + +```csharp +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace WebFixtureTests; + +public class WebHostIntegrationTest : IClassFixture +{ + private readonly BlockingManagedWebHostFixture _fixture; + + public WebHostIntegrationTest(BlockingManagedWebHostFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void HostShouldBeStarted() + { + Assert.True(_fixture.HasValidState()); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.HostBuilderApplicationExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.HostBuilderApplicationExtensions.md new file mode 100644 index 0000000..d439206 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.HostBuilderApplicationExtensions.md @@ -0,0 +1,22 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.HostBuilderApplicationExtensions +example: +- *content +--- + +The following example converts an `IHostApplicationBuilder` to an `IHostBuilder` by calling `ToHostBuilder`. This is useful when working with ASP.NET Core minimal API hosts where the `IHostApplicationBuilder` provides access to the underlying `IHostBuilder` for advanced configuration. + +```csharp +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.Extensions.Hosting; + +namespace WebFixtureTests; + +public class BuilderConverter +{ + public IHostBuilder Convert(IHostApplicationBuilder builder) + { + return builder.ToHostBuilder(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.FakeHttpContextAccessor.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.FakeHttpContextAccessor.md new file mode 100644 index 0000000..597e628 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.FakeHttpContextAccessor.md @@ -0,0 +1,23 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.FakeHttpContextAccessor +example: +- *content +--- + +The following example creates a `FakeHttpContextAccessor` instance for unit testing code that depends on `IHttpContextAccessor`. The fake initializes a `DefaultHttpContext` with `FakeHttpRequestFeature` and `FakeHttpResponseFeature`, providing a complete HTTP context without running a real server. + +```csharp +using System; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http; + +namespace WebFixtureTests; + +public class HttpContextExample +{ + public void Demonstrate() + { + var accessor = new FakeHttpContextAccessor(); + Console.WriteLine(accessor.HttpContext.Request.Method); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.FakeHttpRequestFeature.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.FakeHttpRequestFeature.md new file mode 100644 index 0000000..258eb86 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.FakeHttpRequestFeature.md @@ -0,0 +1,23 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.FakeHttpRequestFeature +example: +- *content +--- + +The following example creates a `FakeHttpRequestFeature` to simulate the HTTP request metadata in a unit test. The feature provides settable properties for the HTTP method, path, query string, and request body, allowing complete control over the request characteristics. + +```csharp +using System; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features; + +namespace WebFixtureTests; + +public class RequestFeatureExample +{ + public void Demonstrate() + { + var request = new FakeHttpRequestFeature(); + Console.WriteLine(request.Method); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.FakeHttpResponseFeature.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.FakeHttpResponseFeature.md new file mode 100644 index 0000000..adbebfa --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.FakeHttpResponseFeature.md @@ -0,0 +1,23 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features.FakeHttpResponseFeature +example: +- *content +--- + +The following example creates a `FakeHttpResponseFeature` to simulate the HTTP response surface in a unit test. The feature provides settable properties for the status code, reason phrase, headers, and response body, allowing the test to inspect what the code under test writes to the response. + +```csharp +using System; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Http.Features; + +namespace WebFixtureTests; + +public class ResponseFeatureExample +{ + public void Demonstrate() + { + var response = new FakeHttpResponseFeature(); + Console.WriteLine(response.StatusCode); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.HttpClientExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.HttpClientExtensions.md new file mode 100644 index 0000000..566e5df --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.HttpClientExtensions.md @@ -0,0 +1,25 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.HttpClientExtensions +example: +- *content +--- + +The following example sends an HTTP request using the `ToHttpResponseMessageAsync` extension on an `HttpClient`. By default it performs a GET request to the root URL ("/"), or you can supply a custom response factory delegate. This method is designed to work with test host fixtures that provide a pre-configured `HttpClient`. + +```csharp +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; + +namespace WebFixtureTests; + +public class HttpClientExample +{ + public async Task DemonstrateAsync(HttpClient client) + { + var response = await client.ToHttpResponseMessageAsync().ConfigureAwait(false); + Console.WriteLine(response.StatusCode); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebHostFixture.md new file mode 100644 index 0000000..36bddc9 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebHostFixture.md @@ -0,0 +1,53 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebHostFixture +example: +- *content +--- + +The following example uses `ManagedWebHostFixture` as the fixture type for a web host test class. The fixture owns the full `IWebHost` lifecycle, starting the web host before tests and disposing it after, making it suitable for integration testing of middleware, controllers, and Razor Pages. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace WebFixtureTests; + +public class MiddlewareTest : WebHostTest +{ + public MiddlewareTest(ManagedWebHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddFakeHttpContextAccessor(); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.UseMiddleware(); + } + + [Fact] + public async Task Middleware_ShouldAddHeader() + { + var pipeline = Application.Build(); + var context = new DefaultHttpContext(); + await pipeline(context); + Assert.True(context.Response.Headers.ContainsKey("X-Test")); + } +} + +public class TestMiddleware +{ + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + context.Response.Headers["X-Test"] = "true"; + await next(context); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebMinimalHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebMinimalHostFixture.md new file mode 100644 index 0000000..860ad0a --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebMinimalHostFixture.md @@ -0,0 +1,44 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebMinimalHostFixture +example: +- *content +--- + +The following example uses `ManagedWebMinimalHostFixture` as the fixture type for a minimal web host test class. The fixture starts and manages a minimal web host, giving the test access to a configured host and the ability to send HTTP requests through the ASP.NET Core pipeline. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace WebFixtureTests; + +public class MinimalApiTest : MinimalWebHostTest +{ + public MinimalApiTest(ManagedWebMinimalHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + } + + protected override void ConfigureHost(IHostApplicationBuilder hb) + { + hb.Services.AddFakeHttpContextAccessor(); + } + + public override void ConfigureApplication(IApplicationBuilder app) + { + app.UseMiddleware(); + } +} + +internal class TestMiddleware +{ + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + await next(context); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.MinimalWebHostTestFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.MinimalWebHostTestFactory.md new file mode 100644 index 0000000..6ba6a8d --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.MinimalWebHostTestFactory.md @@ -0,0 +1,35 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.MinimalWebHostTestFactory +example: +- *content +--- + +Use `MinimalWebHostTestFactory` when a test needs the modern `IHostApplicationBuilder` configuration model and a small in-memory request pipeline. The example supplies a health state through dependency injection, serves it from middleware, and reads the response returned by `RunAsync`. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace HealthEndpoint.Tests; + +public sealed class MinimalWebHostTestFactoryExample +{ + public async Task ReadHealthStateAsync() + { + using var response = await MinimalWebHostTestFactory.RunAsync( + services => services.AddSingleton(new HealthState("healthy")), + app => app.Run(context => + { + var health = context.RequestServices.GetRequiredService(); + return context.Response.WriteAsync(health.Value); + })).ConfigureAwait(false); + + return await response.Content.ReadAsStringAsync().ConfigureAwait(false); + } +} + +public sealed record HealthState(string Value); +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.SelfManagedWebHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.SelfManagedWebHostFixture.md new file mode 100644 index 0000000..a0bbfed --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.SelfManagedWebHostFixture.md @@ -0,0 +1,34 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.SelfManagedWebHostFixture +example: +- *content +--- + +Use `SelfManagedWebHostFixture` when a conventional test pipeline must exist before the server starts. The example builds middleware, explicitly starts the host, and then sends a request through `TestServer`, making the startup boundary and resulting response visible. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Hosting; + +namespace DeferredPipeline.Tests; + +public sealed class SelfManagedWebHostFixtureExample +{ + public async Task StartPipelineWhenReadyAsync() + { + using var host = WebHostTestFactory.Create( + pipelineSetup: app => app.Run(context => + context.Response.WriteAsync("started on demand")), + hostFixture: new SelfManagedWebHostFixture()); + + await host.Host.StartAsync().ConfigureAwait(false); + using var client = host.Host.GetTestClient(); + + return await client.GetStringAsync("/").ConfigureAwait(false); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.SelfManagedWebMinimalHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.SelfManagedWebMinimalHostFixture.md new file mode 100644 index 0000000..633fd47 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.SelfManagedWebMinimalHostFixture.md @@ -0,0 +1,41 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.SelfManagedWebMinimalHostFixture +example: +- *content +--- + +Use `SelfManagedWebMinimalHostFixture` when a minimal test pipeline needs configuration or observation before startup. The example registers endpoint state, creates the pipeline without starting it, then explicitly starts the host and verifies the response through `TestServer`. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace DeferredHealthEndpoint.Tests; + +public sealed class SelfManagedWebMinimalHostFixtureExample +{ + public async Task StartHealthEndpointWhenReadyAsync() + { + using var host = MinimalWebHostTestFactory.Create( + services => services.AddSingleton(new HealthState("healthy")), + pipelineSetup: app => app.Run(context => + { + var health = context.RequestServices.GetRequiredService(); + return context.Response.WriteAsync(health.Value); + }), + hostFixture: new SelfManagedWebMinimalHostFixture()); + + await host.Host.StartAsync().ConfigureAwait(false); + using var client = host.Host.GetTestClient(); + + return await client.GetStringAsync("/").ConfigureAwait(false); + } +} + +public sealed record HealthState(string Value); +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ServiceCollectionExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ServiceCollectionExtensions.md new file mode 100644 index 0000000..1095e01 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ServiceCollectionExtensions.md @@ -0,0 +1,22 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.ServiceCollectionExtensions +example: +- *content +--- + +The following example registers a fake `IHttpContextAccessor` on an `IServiceCollection` so that unit tests can simulate HTTP context without hosting a real server. Call `AddFakeHttpContextAccessor` with the desired service lifetime to control how the accessor is reused across requests. + +```csharp +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.Extensions.DependencyInjection; + +namespace WebFixtureTests; + +public class ExampleFixture +{ + public IServiceCollection ConfigureServices(IServiceCollection services) + { + return services.AddFakeHttpContextAccessor(ServiceLifetime.Scoped); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationFixtureExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationFixtureExtensions.md new file mode 100644 index 0000000..9ddc25f --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationFixtureExtensions.md @@ -0,0 +1,21 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationFixtureExtensions +example: +- *content +--- + +The following example validates that an `IWebApplicationFixture` has a valid initialized state by calling `HasValidState`. The method checks that the host, configure callback, and configure-web-host callback are all non-null. + +```csharp +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; + +namespace WebFixtureTests; + +public class WebAppStateGuard +{ + public bool EnsureFixtureIsReady(IWebApplicationFixture fixture) where TEntryPoint : class + { + return fixture.HasValidState(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationTestFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationTestFactory.md new file mode 100644 index 0000000..57bc791 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationTestFactory.md @@ -0,0 +1,52 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationTestFactory +example: +- *content +--- + +The test project references a minimal ASP.NET Core application with a `/health` endpoint. `WebApplicationTestFactory` runs that real entry point on `TestServer`, applies a test-only service override through `IWebHostBuilder`, and gives the test an owned host from which it creates an HTTP client. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace CatalogApi.Tests; + +public sealed class WebApplicationTestFactoryExample +{ + [Fact] + public async Task Create_UsesTheApplicationPipelineAndTestOverrides() + { + using var application = WebApplicationTestFactory.Create(builder => + { + builder.ConfigureServices(services => + services.AddSingleton(new CatalogStatus("test-ready"))); + }); + using var client = application.Host.GetTestClient(); + + var body = await client.GetStringAsync("/health").ConfigureAwait(false); + + Assert.Equal("test-ready", body); + } +} + +public sealed record CatalogStatus(string Value); + +public sealed class CatalogProgram +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddSingleton(new CatalogStatus("ready")); + + var app = builder.Build(); + app.MapGet("/health", (CatalogStatus status) => status.Value); + app.Run(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostFixtureExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostFixtureExtensions.md new file mode 100644 index 0000000..e835847 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostFixtureExtensions.md @@ -0,0 +1,21 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostFixtureExtensions +example: +- *content +--- + +The following example validates that an `IWebHostFixture` has a valid initialized state by calling `HasValidState`. The method checks that the underlying generic host fixture is valid and that the application configure callback is set. + +```csharp +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; + +namespace WebFixtureTests; + +public class WebHostStateGuard +{ + public bool EnsureFixtureIsReady(IWebHostFixture fixture) + { + return fixture.HasValidState(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostTestFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostTestFactory.md new file mode 100644 index 0000000..0f08606 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostTestFactory.md @@ -0,0 +1,35 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebHostTestFactory +example: +- *content +--- + +Use `WebHostTestFactory` when the test defines a conventional ASP.NET Core service collection and middleware pipeline instead of bootstrapping an application entry point. `RunAsync` starts that pipeline on `TestServer`, sends a request, and returns the response so the caller can verify middleware behavior. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace RequestPipeline.Tests; + +public sealed class WebHostTestFactoryExample +{ + public async Task InvokeStatusMiddlewareAsync() + { + using var response = await WebHostTestFactory.RunAsync( + services => services.AddSingleton(new PipelineStatus("ready")), + app => app.Run(context => + { + var status = context.RequestServices.GetRequiredService(); + return context.Response.WriteAsync(status.Value); + })).ConfigureAwait(false); + + return await response.Content.ReadAsStringAsync().ConfigureAwait(false); + } +} + +public sealed record PipelineStatus(string Value); +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebMinimalHostFixtureExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebMinimalHostFixtureExtensions.md new file mode 100644 index 0000000..43a1464 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebMinimalHostFixtureExtensions.md @@ -0,0 +1,21 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebMinimalHostFixtureExtensions +example: +- *content +--- + +The following example validates that an `IWebMinimalHostFixture` has a valid initialized state by calling `HasValidState`. The method checks the minimal host fixture state and additionally verifies that the application callback and application pipeline are configured. + +```csharp +using Codebelt.Extensions.Xunit.Hosting.AspNetCore; + +namespace WebFixtureTests; + +public class WebMinimalHostStateGuard +{ + public bool EnsureFixtureIsReady(IWebMinimalHostFixture fixture) + { + return fixture.HasValidState(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.BlockingManagedApplicationFixture%601.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.BlockingManagedApplicationFixture%601.md new file mode 100644 index 0000000..b0a4f08 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.BlockingManagedApplicationFixture%601.md @@ -0,0 +1,48 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.BlockingManagedApplicationFixture`1 +example: +- *content +--- + +The test project references a worker application's entry point and shares one bootstrapped host through xUnit's class-fixture lifetime. `BlockingManagedApplicationFixture` waits until the host is ready before constructing the test class, so each test can immediately resolve services registered by the real application. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace InventoryWorker.Tests; + +public sealed class InventoryWorkerTest : IClassFixture> +{ + private readonly BlockingManagedApplicationFixture _fixture; + + public InventoryWorkerTest(BlockingManagedApplicationFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void Host_ContainsApplicationService() + { + var identity = _fixture.Host.Services.GetRequiredService(); + + Assert.Equal("Inventory worker", identity.Name); + } +} + +public sealed record WorkerIdentity(string Name); + +public sealed class WorkerProgram +{ + public static void Main(string[] args) + { + var builder = Host.CreateApplicationBuilder(args); + builder.Services.AddSingleton(new WorkerIdentity("Inventory worker")); + + using var host = builder.Build(); + host.Run(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.GenericHostFixtureExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.GenericHostFixtureExtensions.md new file mode 100644 index 0000000..dac9d14 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.GenericHostFixtureExtensions.md @@ -0,0 +1,21 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.GenericHostFixtureExtensions +example: +- *content +--- + +The following example validates that an `IGenericHostFixture` has been fully configured by calling `HasValidState` on the fixture. The method returns `true` only when both `ConfigureServicesCallback` and `ConfigureHostCallback` are non-null. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; + +namespace HostFixtureTests; + +public class HostStateGuard +{ + public bool EnsureFixtureIsReady(IGenericHostFixture fixture) + { + return fixture.HasValidState(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.HostTestFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.HostTestFactory.md new file mode 100644 index 0000000..49e4c6a --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.HostTestFactory.md @@ -0,0 +1,28 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.HostTestFactory +example: +- *content +--- + +Use `HostTestFactory` when the test owns the Generic Host setup instead of loading an existing application entry point. This example registers a worker-facing status service, creates a managed host context, and reads the configured value through the same service provider the hosted application would use. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace WorkerServices.Tests; + +public sealed class HostTestFactoryExample +{ + public string ResolveWorkerStatus() + { + using var host = HostTestFactory.Create( + services => services.AddSingleton(new WorkerStatus("ready"))); + + var status = host.Host.Services.GetRequiredService(); + return status.Value; + } +} + +public sealed record WorkerStatus(string Value); +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.LoggerExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.LoggerExtensions.md new file mode 100644 index 0000000..3a60966 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.LoggerExtensions.md @@ -0,0 +1,28 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.LoggerExtensions +example: +- *content +--- + +The following example retrieves the captured log entries from an `ILogger` after xUnit test logging has been registered. Use `GetTestStore()` on an untyped `ILogger` to search by category name, or `GetTestStore()` to retrieve entries for a specific typed logger category. + +```csharp +using Codebelt.Extensions.Xunit; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.Logging; + +namespace HostFixtureTests; + +public class LogInspector +{ + public ITestStore GetEntries(ILogger logger) + { + return logger.GetTestStore(); + } + + public ITestStore GetEntries(ILogger logger) + { + return logger.GetTestStore(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedHostFixture.md new file mode 100644 index 0000000..4514482 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedHostFixture.md @@ -0,0 +1,48 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.ManagedHostFixture +example: +- *content +--- + +The following example uses `ManagedHostFixture` as the fixture type for a generic host test class. The fixture owns the full `IHost` lifecycle, starting the host before the test runs and disposing it afterward. This is the standard pattern for testing services that depend on dependency injection and need a running host. + +```csharp +using System.Threading; +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace HostFixtureTests; + +public class BackgroundServiceTest : HostTest +{ + public BackgroundServiceTest(ManagedHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddHostedService(); + services.AddXunitTestLogging(); + } + + [Fact] + public void Host_ShouldBeRunning() + { + Assert.NotNull(Host); + } +} + +public class TestWorker : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + await Task.Delay(100, stoppingToken); + } + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedMinimalHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedMinimalHostFixture.md new file mode 100644 index 0000000..30d5973 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedMinimalHostFixture.md @@ -0,0 +1,37 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.ManagedMinimalHostFixture +example: +- *content +--- + +The following example uses `ManagedMinimalHostFixture` as the fixture type for a minimal host test class. The fixture automatically starts the minimal host and makes it available for the test duration, then disposes it. This is ideal for lean test scenarios that do not need the full generic host. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace HostFixtureTests; + +public class MinimalWorkerTest : MinimalHostTest +{ + public MinimalWorkerTest(ManagedMinimalHostFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + { + } + + protected override void ConfigureHost(IHostApplicationBuilder hb) + { + hb.Services.AddSingleton(); + hb.Services.AddXunitTestLogging(); + } + + [Fact] + public void Host_ShouldNotBeNull() + { + Assert.NotNull(Host); + } +} + +public class MetricsTracker; +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.MinimalHostFixtureExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.MinimalHostFixtureExtensions.md new file mode 100644 index 0000000..08d69f2 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.MinimalHostFixtureExtensions.md @@ -0,0 +1,21 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.MinimalHostFixtureExtensions +example: +- *content +--- + +The following example validates that an `IMinimalHostFixture` has been fully initialized by calling `HasValidState` on the fixture. The method checks that the host, configure-host callback, and configure callback are all non-null. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; + +namespace HostFixtureTests; + +public class HostStateGuard +{ + public bool EnsureFixtureIsReady(IMinimalHostFixture fixture) + { + return fixture.HasValidState(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.MinimalHostTestFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.MinimalHostTestFactory.md new file mode 100644 index 0000000..f4c5342 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.MinimalHostTestFactory.md @@ -0,0 +1,26 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.MinimalHostTestFactory +example: +- *content +--- + +The following example uses `MinimalHostTestFactory` to create a lightweight host context for testing a simple service. Unlike the generic host factory, the minimal host avoids the full `IHostBuilder` pipeline and is suitable for isolated unit tests that only need dependency injection and logging. + +```csharp +using System; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace HostFixtureTests; + +public class MinimalHostFactoryExample +{ + public void Demonstrate() + { + using var host = MinimalHostTestFactory.Create( + services => services.AddSingleton(new Version(1, 0, 0))); + var version = host.Host.Services.GetRequiredService(); + Console.WriteLine(version); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.SelfManagedHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.SelfManagedHostFixture.md new file mode 100644 index 0000000..40197bf --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.SelfManagedHostFixture.md @@ -0,0 +1,34 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.SelfManagedHostFixture +example: +- *content +--- + +Use `SelfManagedHostFixture` when the host must be built before it is started. The example attaches an `ApplicationStarted` observer first, verifies that factory creation did not start the host, and then starts it explicitly so the lifecycle transition is under test control. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace WorkerLifecycle.Tests; + +public sealed class SelfManagedHostFixtureExample +{ + public async Task StartAfterAttachingObserverAsync() + { + using var host = HostTestFactory.Create( + hostFixture: new SelfManagedHostFixture()); + + var started = false; + var lifetime = host.Host.Services.GetRequiredService(); + using var registration = lifetime.ApplicationStarted.Register(() => started = true); + + Assert.False(started); + await host.Host.StartAsync().ConfigureAwait(false); + Assert.True(started); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.SelfManagedMinimalHostFixture.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.SelfManagedMinimalHostFixture.md new file mode 100644 index 0000000..322d457 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.SelfManagedMinimalHostFixture.md @@ -0,0 +1,36 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.SelfManagedMinimalHostFixture +example: +- *content +--- + +Use `SelfManagedMinimalHostFixture` when a minimal host should be configured and inspected before startup. This example verifies a service registered through `MinimalHostTestFactory`, starts the host only after the pre-start assertion, and confirms the same service remains available from the running host. + +```csharp +using System.Threading.Tasks; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace MinimalWorkerLifecycle.Tests; + +public sealed class SelfManagedMinimalHostFixtureExample +{ + public async Task ConfigureBeforeStartingAsync() + { + using var host = MinimalHostTestFactory.Create( + services => services.AddSingleton(new StartupState("configured")), + hostFixture: new SelfManagedMinimalHostFixture()); + + var beforeStart = host.Host.Services.GetRequiredService(); + Assert.Equal("configured", beforeStart.Value); + + await host.Host.StartAsync().ConfigureAwait(false); + + var afterStart = host.Host.Services.GetRequiredService(); + Assert.Same(beforeStart, afterStart); + } +} + +public sealed record StartupState(string Value); +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ServiceCollectionExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ServiceCollectionExtensions.md new file mode 100644 index 0000000..1709956 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ServiceCollectionExtensions.md @@ -0,0 +1,25 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.ServiceCollectionExtensions +example: +- *content +--- + +The following example registers xUnit test logging and output helper services on an `IServiceCollection` so that `ILogger` output is captured during a test run. Call `AddXunitTestLogging` with a minimum `LogLevel`, then register the `ITestOutputHelperAccessor` using `AddXunitTestLoggingOutputHelperAccessor` or a custom implementation with `AddXunitTestLoggingOutputHelperAccessor`. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace HostFixtureTests; + +public class ExampleFixture +{ + public IServiceCollection ConfigureServices(IServiceCollection services) + { + services.AddXunitTestLogging(LogLevel.Information); + services.AddXunitTestLoggingOutputHelperAccessor(); + return services; + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ServiceProviderExtensions.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ServiceProviderExtensions.md new file mode 100644 index 0000000..cc71f59 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ServiceProviderExtensions.md @@ -0,0 +1,23 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.ServiceProviderExtensions +example: +- *content +--- + +The following example retrieves a scoped service of type `T` from an `IServiceProvider` by calling `GetRequiredScopedService`. The extension creates a new scope, resolves the service from the scope's provider, and disposes the scope before returning. + +```csharp +using System; +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace HostFixtureTests; + +public class ServiceResolver +{ + public T Resolve(IServiceProvider provider) where T : notnull + { + return provider.GetRequiredScopedService(); + } +} +``` diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.XunitTestLoggerEntry.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.XunitTestLoggerEntry.md new file mode 100644 index 0000000..c9ed9d9 --- /dev/null +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.XunitTestLoggerEntry.md @@ -0,0 +1,23 @@ +--- +uid: Codebelt.Extensions.Xunit.Hosting.XunitTestLoggerEntry +example: +- *content +--- + +The following example creates an `XunitTestLoggerEntry` record and formats its message as a string. Each entry captures the `LogLevel`, `EventId`, and message text of a single log statement written during a test, and the record's `ToString()` method returns the message for easy inspection in assertions. + +```csharp +using Codebelt.Extensions.Xunit.Hosting; +using Microsoft.Extensions.Logging; + +namespace HostFixtureTests; + +public class LogEntryExample +{ + public string FormatEntry() + { + var entry = new XunitTestLoggerEntry(LogLevel.Warning, new EventId(1001, "MyEvent"), "Something unexpected happened."); + return entry.ToString(); + } +} +``` diff --git a/.docfx/docfx.json b/.docfx/docfx.json index f714667..a47b04d 100644 --- a/.docfx/docfx.json +++ b/.docfx/docfx.json @@ -27,12 +27,13 @@ { "files": [ "api/**/*.yml", - "api/**/*.md", "packages/**/*.md", "toc.yml", "*.md" ], "exclude": [ + "api/namespaces/**", + "api/types/**", "bin/**", "obj/**" ] @@ -70,7 +71,8 @@ "overwrite": [ { "files": [ - "api/namespaces/**.md" + "api/namespaces/**/*.md", + "api/types/**/*.md" ], "exclude": [ "obj/**", diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2e6351e..0c3aebe 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -177,6 +177,28 @@ Internal classes and methods must be validated by exercising the public API that - Public entry points provide sufficient coverage of internal code paths. - The internal implementation exists solely as a helper or utility for public-facing functionality. +## 10. ExcludeFromCodeCoverage Prohibition + +**Do not use `ExcludeFromCodeCoverage` attribute on any code.** This includes: + +- Test classes or test methods +- Production code +- Configuration code +- Any other code path + +### Rationale + +- Excluding code from coverage hides gaps and creates false confidence in test completeness. +- If a code path cannot or should not be tested, refactor the code to eliminate that path rather than hiding it from metrics. +- Every executable line should be covered by tests or be genuinely unreachable (dead code to be removed). + +### Alternative Approaches + +- **Untestable code paths**: Refactor to separate concerns and eliminate the untestable path. +- **External dependencies**: Use test doubles (fakes, stubs, spies) instead of excluding from coverage. +- **Configuration-only code**: Move to configuration files or extract into testable methods. +- **Generated or third-party code**: These should not be in the primary codebase; use NuGet packages or dedicated vendor folders if necessary. + --- description: 'Writing Performance Tests' applyTo: "tuning/**, **/*Benchmark*.cs" diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index d1cd6b4..da800c8 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -203,7 +203,8 @@ jobs: security-events: write deploy: - if: github.event_name != 'pull_request' + # Avoid skipped optional jobs (for example disabled macOS matrix runs) from suppressing deployment. + if: ${{ always() && github.event_name != 'pull_request' && needs.build.result == 'success' && needs.pack.result == 'success' && needs.test_qualitygate.result == 'success' && needs.sonarcloud.result == 'success' && needs.codecov.result == 'success' && needs.codeql.result == 'success' }} name: call-nuget needs: [build, pack, test_qualitygate, sonarcloud, codecov, codeql] uses: codebeltnet/jobs-nuget-push/.github/workflows/default.yml@v3 diff --git a/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt index b9264a2..5cba9a2 100644 --- a/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 11.1.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 11.1.0 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt index 89a4695..50257c6 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 11.1.1 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 11.1.0 Availability: .NET 10 and .NET 9 diff --git a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/README.md b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/README.md index 688a810..5491bf6 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/README.md +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/README.md @@ -13,7 +13,7 @@ It is, by heart, free, flexible and built to extend and boost your agile codebel The `Codebelt.Extensions.Xunit.Hosting.AspNetCore` namespace contains types that provides a uniform way of doing unit testing that depends on ASP.NET Core and used in conjunction with Microsoft Dependency Injection. The namespace relates to the `Microsoft.AspNetCore.TestHost` namespace. -Perhaps even more convenient than what [WebApplicationFactory](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1) has to offer? +`WebApplicationTestFactory.Create` is a lightweight alternative for focused integration tests that prefer inline `IWebHostBuilder` customization and Codebelt's common `IHostTest` model. It is not a drop-in replacement for [WebApplicationFactory](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1): use Microsoft's factory when reusable derived factories, `CreateClient` options, `WithWebHostBuilder`, or MVC content-root conventions are central to the test suite. More documentation available at our documentation site: diff --git a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt index 21428c6..1da8645 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 11.1.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 11.1.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 diff --git a/.nuget/Codebelt.Extensions.Xunit.Hosting/README.md b/.nuget/Codebelt.Extensions.Xunit.Hosting/README.md index 27c0ece..2044c5c 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting/README.md +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting/README.md @@ -11,7 +11,9 @@ It is, by heart, free, flexible and built to extend and boost your agile codebel ## **Codebelt.Extensions.Xunit.Hosting** for .NET -The `Codebelt.Extensions.Xunit.Hosting` namespace contains types that provides a uniform way of doing unit testing that is used in conjunction with Microsoft Dependency Injection. The namespace relates to the `Xunit.Abstractions` namespace. +The `Codebelt.Extensions.Xunit.Hosting` namespace contains types that provides a uniform way of doing unit testing that is used in conjunction with Microsoft Dependency Injection. The namespace relates to the `Xunit.Abstractions` namespace. + +Use `ApplicationTestFactory.Create` for a focused integration test against an existing console, worker or Generic Host application's `Program` assembly. It brings the entry-point testing pattern commonly associated with ASP.NET Core to the rest of the .NET application stack, while `ApplicationTest` and `BlockingManagedApplicationFixture` provide the reusable xUnit class-fixture form. More documentation available at our documentation site: diff --git a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt index 2e71c58..e6537e3 100644 --- a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 11.1.1 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED Dependencies have been upgraded to the latest compatible versions for all supported target frameworks (TFMs) + Version: 11.1.0 Availability: .NET 10, .NET 9 and .NET Standard 2.0 diff --git a/AGENTS.md b/AGENTS.md index 42d5fbb..c47e35a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -265,3 +265,63 @@ See `.github/copilot-instructions.md` for detailed guidelines on: - Writing unit tests - Writing performance tests (BenchmarkDotNet) - Writing XML documentation + + +## DocFX Documentation Maintenance + +When changing public .NET APIs, keep the DocFX documentation current in the same change set. + +Documentation updates must cover public API only. Do not document private or internal types or members. Do not create namespace overview pages for namespaces that contain no public API. + +Public non-abstraction types — including enums, structs, records, plain classes, and static extension containers — are valid documentation targets. Generic public types and generic extension methods are valid documentation targets too. Do not exclude a type solely because it is generic or because reflection reports it as abstract and sealed (that is the IL pattern for a static class). + +For public non-abstraction types, include at least one realistic, copy/paste-ready usage example on the generated type page/overwrite section for that type UID. For example, a public `Class1` requires an example on the `Class1` API page, not only on the namespace page. Prefer deriving examples from existing unit, functional, or integration tests, but convert test code into real-life consumer-oriented usage. + +Missing type examples must be added through per-type DocFX overwrite files under `.docfx/api/types/{TypeUid}.md` in Codebelt repositories. Namespace overview text and `Extension Members` tables are not substitutes for type-page examples. + +Public extension methods must have examples too. Listing an extension method in an `Extension Members` table is required, but it is not enough. + +All added or changed code samples must be deterministic and verified to compile. Do not add pseudo-code, ellipses, hidden test helpers, or examples that rely on unverified behavior. + +Compilation is necessary but not sufficient. Do not present runtime implementation names such as `services.GetType().Name` or `host.GetType().FullName` as the example outcome. Show application behavior, configured state, a resolved domain service, an HTTP response, or another result that explains why a caller uses the API. Application-entry-point examples must not declare an empty local `Program` type merely to compile; show a real entry point or clearly identify the referenced application project. + +Every namespace containing public API must have a DocFX namespace overview page named after the namespace, such as `X.Y.Z.md`, under `.docfx/api/namespaces/`, using DocFX overwrite front matter with the namespace `uid`. + +Namespace pages must identify key entry points from release notes, package documentation, public factories/builders, and strong functional tests, then help readers choose among adjacent workflows. When the package complements a well-known upstream API, compare concrete acquisition, customization, lifecycle, and sharing tradeoffs from current official guidance; do not claim drop-in replacement compatibility without evidence. + +Namespaces exposing public extension methods must document those extension members at namespace level. The namespace page must include an `Extension Members` table listing the extended type, the extension marker, and the public extension methods. Extension members are rendered under the heading `Extension Members`. + +Both namespace overwrite files and type overwrite files are required deliverables in the same run. Generating only namespace pages or only type pages is incomplete. + +`docfx.json` must keep namespace and type overwrite files in separate subdirectories. `build.overwrite` must include both `api/namespaces/**/*.md` (for namespace pages) and `api/types/**/*.md` (for type pages). `build.content` must exclude both `api/namespaces/**` and `api/types/**` to prevent overwrite Markdown from being treated as conceptual content. Do not use `api/**/*.md` under `build.overwrite` or `build.content`. + +Availability must be documented by referencing the appropriate include file when one exists, or by adding explicit availability text when no suitable include exists. Availability must reflect the actual target frameworks, conditional compilation, and project configuration. + +For conditionally compiled APIs, choose the executable test framework from the asset that contains the API. Inspect the preprocessor condition, project TFMs, package `lib/` assets, and resolved consumer asset before changing a sample. For APIs under `NETSTANDARD2_0` or `NETSTANDARD2_0_OR_GREATER`, when modern `lib/netX.0/` assets also exist, use `net48` (or another supported .NET Framework target from `net462` onward) so the consumer selects `lib/netstandard2.0/`. Never use `netstandard*` as an executable target, and never use a modern `netX.0` target when it selects an asset where the API is absent. For other TFM guards, select a runnable consumer TFM that resolves to the containing asset and confirm that selection from restore or build evidence. + +Preserve manual documentation edits. Prefer additive changes, but correct stale or contradictory information so documentation remains accurate. + +Preserve working Markdown links, `Related:` references, and historical URL citations during prose rewrites. Remove or replace a URL only after directly verifying that the current destination returns HTTP 404. Timeouts, 403s, rate limits, DNS failures, and other lookup problems are not removal evidence. + +Interim scratch artifacts do not belong in the repository working tree. Store assessment queues, project manifests, review reports, captured validator output, progress notes, and one-off helper scripts in temp or session storage instead. New working-tree files are only legitimate when they are the managed `AGENTS.md` block, the active `docfx.json`, the deterministic `skip-compile-allowlist.json` waiver file when one is truly required, or DocFX-authored namespace/type Markdown that maps to a real public namespace or type. Everything else is blocking cleanup work, not a documentation deliverable. The validator auto-detects generic-arity type families (such as `MutableTuple`1`..`MutableTuple`N`) and skips redundant sibling examples from the public API surface alone, so no family-skip manifest is ever written into the repository. + +Skip markers are waivers, not fixes. A skip marker only suppresses compilation when it both existed before the current run and matches an entry in `.docfx/skip-compile-allowlist.json`. Each allowlist entry must include `diagnosticCode`, `filePath`, `uid` or `symbol`, `reason`, `approval`, and `lifetime` (`temporary` or `permanent`). Newly introduced or unallowlisted skip markers remain fail-level diagnostics and do not permit a completion claim. + +Do not emit a final report, audit result, completion summary, or handoff while `summary.canClaimCompletion` is false, `summary.remainingWorkItems` is greater than zero, `summary.remainingGates` is non-empty, `summary.fullVerificationRan` is false, fail-level diagnostics remain, `summary.newlyIntroducedSkipMarkers` is non-zero, or `summary.interimArtifacts` is non-zero. Large queues, many changed files, repetitive next steps, long runtimes, context pressure, session length, task size, or a "stable queue" are not valid stop reasons; the next action must be another remediation batch, a validator rerun, a validator/tooling fix, or a true blocker with exact evidence. + +Context pressure is not a completion condition. If the session feels constrained while work remains, continue with a smaller deterministic batch, regenerate deterministic queue state such as `--assessment-queue`, `--project-manifest`, or the active dry-run manifest/review pair, or report a true tooling failure with the exact command, exit code, and output. When naming a queue-state regeneration command, resolve it to a concrete temp/session path instead of leaving `` as a placeholder. Do not stop with phrases like "given context constraints", "best done in a follow-up", "remaining work requires authoring", "this is a massive task", or "I will provide a focused summary". A context-sized handoff while work remains is `FAIL_CONTEXT_HANDOFF_WITH_REMAINING_WORK`; the remediation is to continue with a smaller deterministic batch. + +Before completing documentation work, run the relevant verification commands, normally: + +```bash +dotnet build +dotnet test +dotnet run --file /scripts/docfx.cs -- --repo-root . --build-api-model --validate-samples --verify-docfx-build +``` + +Codebelt repositories are normally strong-name signed with a `.snk` file in the repository root on the main author's codespace. Preserve and copy that root `.snk` file when building a temporary copy. If the repository or temp copy has no root `.snk`, run build and test verification with `-p:SkipSignAssembly=true`, for example `dotnet build -p:SkipSignAssembly=true` and `dotnet test -p:SkipSignAssembly=true`. + +The final DocFX verification must run outside the working tree when possible. The `--verify-docfx-build` option copies the repository to a temp workspace, runs DocFX against the resolved `docfx.json` there, and removes the temp workspace afterward so generated API YAML, manifest files, and site output do not flood git status. Do not call the work complete until the final JSON reports `summary.fullVerificationRan: true`, `summary.canClaimCompletion: true`, `summary.remainingWorkItems: 0`, an empty `summary.remainingGates`, an empty `summary.remainingDiagnosticsByCode`, `summary.newlyIntroducedSkipMarkers: 0`, and `summary.interimArtifacts: 0`. + +If a command cannot be run, report the exact limitation or failure instead of claiming the documentation was verified. + diff --git a/CHANGELOG.md b/CHANGELOG.md index a35bd85..90b14c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,30 @@ 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. +## [11.1.1] - 2026-06-25 + +This is a patch release that improves documentation clarity, enhances DocFX infrastructure, and updates test framework dependencies. All changes are non-breaking service refinements. + +### Added + +- Prohibition on ExcludeFromCodeCoverage attribute usage in AGENTS.md agent guidance documentation, +- DocFX documentation workflow section in AGENTS.md documenting the complete maintenance and verification process for public API documentation. + +### Changed + +- Upgraded Microsoft.NET.Test.Sdk from 18.6.0 to 18.7.0 for improved test framework capabilities, +- Updated NGINX base image and coordinated package version updates for improved compatibility, +- Expanded DocFX namespace pages with improved entry-point guidance, usage patterns, and extension member tables, +- Configured DocFX build system to include newly generated type-level overwrite files for comprehensive public API documentation, +- Enhanced Hosting package README with clearer usage guidance for entry-point factory patterns and fixture options, +- Enhanced AspNetCore package README to clarify lightweight positioning versus WebApplicationFactory and decision criteria. + +### Fixed + +- Corrected ApplicationHostFactory XML documentation comments to accurately reflect that methods create and configure the host without starting it, +- Fixed changelog fixture descriptions to accurately state that BlockingManagedApplicationFixture and BlockingManagedWebApplicationFixture provide blocking implementation, +- Fixed DocFX overwrite glob pattern configuration to properly resolve api/namespaces/**/*.md files. + ## [11.1.0] - 2026-06-05 This is a minor release that brings WebApplicationFactory-like integration testing patterns to the entire .NET application stack — not just ASP.NET Core. ApplicationHostFactory and ApplicationTest abstractions enable Program.cs-based testing for Generic Host scenarios, while WebApplicationTest provides the equivalent TestServer experience for ASP.NET Core. Both patterns support modern minimal hosting and legacy Startup.cs configurations, with comprehensive bootstrapper reference applications and functional test coverage demonstrating real-world testing scenarios. @@ -17,12 +41,12 @@ This is a minor release that brings WebApplicationFactory-like integration testi - ApplicationTest{TEntryPoint,T} base classes in the Codebelt.Extensions.Xunit.Hosting namespace for host integration testing patterns with generic and non-generic variants, - ApplicationTestFactory class in the Codebelt.Extensions.Xunit.Hosting namespace for static factory methods to create host test instances, - IApplicationFixture{TEntryPoint} interface in the Codebelt.Extensions.Xunit.Hosting namespace for fixture-based host lifecycle management, -- BlockingManagedApplicationFixture{TEntryPoint} class in the Codebelt.Extensions.Xunit.Hosting namespace providing non-blocking host fixture implementation, +- BlockingManagedApplicationFixture{TEntryPoint} class in the Codebelt.Extensions.Xunit.Hosting namespace providing blocking host fixture implementation, - ApplicationFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting namespace providing convenient fixture setup methods, - WebApplicationTest{TEntryPoint,T} base classes in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace for ASP.NET Core Program.cs-based TestServer testing, - WebApplicationTestFactory class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace for static factory methods to create web application test instances, - IWebApplicationFixture{TEntryPoint} interface in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace for web application fixture lifecycle management, -- BlockingManagedWebApplicationFixture{TEntryPoint} class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace providing non-blocking web fixture implementation, +- BlockingManagedWebApplicationFixture{TEntryPoint} class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace providing blocking web fixture implementation, - WebApplicationFixtureExtensions class in the Codebelt.Extensions.Xunit.Hosting.AspNetCore namespace providing convenient web fixture setup methods, - Eight bootstrapper reference applications demonstrating host patterns: BootstrapperConsole.App (classic Startup pattern), BootstrapperMinimalConsole.App (minimal hosting), BootstrapperWorker.App (BackgroundService with Startup), BootstrapperMinimalWorker.App (minimal worker service), BootstrapperWeb.App (ASP.NET Core with Startup), BootstrapperMinimalWeb.App (ASP.NET Core minimal), BootstrapperClassicProgram.App (top-level statements), and BootstrapperProgram.App (advanced customization), - Comprehensive functional test coverage for hosting abstractions and integration patterns across Generic Host and ASP.NET Core scenarios, including all bootstrapper configurations. @@ -411,7 +435,8 @@ This major release is first and foremost focused on ironing out any wrinkles tha -[Unreleased]: https://github.com/codebeltnet/xunit/compare/v11.1.0...HEAD +[Unreleased]: https://github.com/codebeltnet/xunit/compare/v11.1.1...HEAD +[11.1.1]: https://github.com/codebeltnet/xunit/compare/v11.1.0...v11.1.1 [11.1.0]: https://github.com/codebeltnet/xunit/compare/v11.0.10...v11.1.0 [11.0.10]: https://github.com/codebeltnet/xunit/compare/v11.0.9...v11.0.10 [11.0.9]: https://github.com/codebeltnet/xunit/compare/v11.0.8...v11.0.9 diff --git a/Directory.Packages.props b/Directory.Packages.props index 0ae09a4..278cd44 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,48 +1,48 @@ - - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Codebelt.Extensions.Xunit.Hosting/ApplicationHostFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting/ApplicationHostFactory.cs index 022048d..15236cb 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/ApplicationHostFactory.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/ApplicationHostFactory.cs @@ -12,7 +12,7 @@ namespace Codebelt.Extensions.Xunit.Hosting; public static class ApplicationHostFactory { /// - /// Creates, configures, builds and starts an from the assembly containing . + /// Creates, configures and builds an from the assembly containing . /// /// A type in the entry point assembly of the application. /// The delegate that provides a way to override the before the application is built. @@ -26,7 +26,7 @@ public static IHost Create(Action configureHost) wher } /// - /// Creates, configures, builds and starts an from the assembly containing . + /// Creates, configures and builds an from the assembly containing . /// /// A type in the entry point assembly of the application. /// The delegate that provides a way to override the before the application is built.