diff --git a/.docfx/Dockerfile.docfx b/.docfx/Dockerfile.docfx index a425700..3ebeaa3 100644 --- a/.docfx/Dockerfile.docfx +++ b/.docfx/Dockerfile.docfx @@ -1,9 +1,9 @@ -ARG NGINX_VERSION=1.31-alpine +ARG NGINX_VERSION=1.31-alpine FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base RUN rm -rf /usr/share/nginx/html/* -FROM --platform=$BUILDPLATFORM codebeltnet/docfx:2.78.4 AS build +FROM --platform=$BUILDPLATFORM codebeltnet/docfx:2.78.5 AS build ADD [".", "docfx"] diff --git a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.md b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.md index 9a8a14f..66ee465 100644 --- a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.md +++ b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.AspNetCore.md @@ -17,7 +17,7 @@ Complements: [ASP.NET Core integration tests](https://learn.microsoft.com/en-us/ |---|---|---| |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 `ManagedWebApplicationFixture`|Opt-in entrypoint-owned startup while the fixture exposes `TestServer`.| +|Share an existing application across an xUnit test class|`WebApplicationTest` with `ManagedWebApplicationFixture`|Uses entrypoint-owned startup while the fixture exposes `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.| @@ -41,11 +41,10 @@ ASP.NET Core host fixtures follow the same lifecycle naming convention as the ho |---|---| |`Managed`|The fixture owns host creation, configuration and disposal while the application entry point owns startup; test-host consumption starts the deferred host when needed.| |`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.| - -For the current minor release, the existing `WebApplicationTestFactory` and blocking fixture paths preserve legacy startup behavior. Use `ManagedWebApplicationFixture` explicitly when the real `Main` method should own startup; fixture setup remains lazy and test-host consumption starts the deferred host. `BlockingManagedWebApplicationFixture` remains available as an obsolete compatibility fixture and should be removed or changed in the next major release. - -`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. + +`WebApplicationTestFactory` uses `ManagedWebApplicationFixture` by default, so the real application entry point owns startup and fixture setup remains lazy. Use `ManagedWebApplicationFixture` explicitly with `WebApplicationTest` when tests share the application context; use a `SelfManaged` fixture when the test must control startup itself. + +`BlockingManagedWebHostFixture` remains the opt-in blocking variant for the lower-level web host fixture family. It is separate from the application-entry-point fixture, which uses `ManagedWebApplicationFixture` for entrypoint-owned startup. ### Extension Members diff --git a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md index a9d1352..5f22c93 100644 --- a/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md +++ b/.docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md @@ -16,7 +16,7 @@ Complements: [xUnit: Shared Context between Tests](https://xunit.net/docs/shared |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 `ManagedApplicationFixture`|Opt-in entrypoint-owned startup through the new managed fixture while retaining configuration and service access.| +|Share an existing application across an xUnit test class|`ApplicationTest` with `ManagedApplicationFixture`|Uses entrypoint-owned startup 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.| @@ -29,9 +29,8 @@ Host fixtures follow a lifecycle naming convention: |---|---| |`Managed`|The fixture owns host creation, configuration and disposal while the application entry point owns startup; test-host consumption starts the deferred host when needed.| |`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.| - -For the current minor release, the existing `ApplicationTestFactory` and blocking fixture paths preserve legacy startup behavior. Use `ManagedApplicationFixture` explicitly when the real `Main` method should own startup; fixture setup remains lazy and test-host consumption starts the deferred host. `BlockingManagedApplicationFixture` remains available as an obsolete compatibility fixture and should be removed or changed in the next major release. + +`ApplicationTestFactory` uses `ManagedApplicationFixture` by default, so the real application entry point owns startup and fixture setup remains lazy. Use `ManagedApplicationFixture` explicitly with `ApplicationTest` when tests share the application context; use a `SelfManaged` fixture when the test must control startup itself. ### Extension Members diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationHostFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationHostFactory.md index 9bf375d..afe7e9f 100644 --- a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationHostFactory.md +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationHostFactory.md @@ -4,7 +4,7 @@ example: - *content --- -The test project references a worker application's entry-point assembly. `ApplicationHostFactory.Create` preserves the current minor-release compatibility path, including direct use of an application's `CreateHostBuilder` when it is available. When the application entry point should own startup, pass `ManagedApplicationFixture` to `ApplicationTestFactory.Create`; the fixture opts into the deferred path without changing the existing factory method signature. The compatibility path is intentionally retained until it can be removed or changed in the next major release. Because this lower-level factory returns the host directly, the caller still owns disposal. +The test project references a worker application's entry-point assembly. `ApplicationHostFactory.Create` preserves the compatibility path, including direct use of an application's `CreateHostBuilder` when it is available. `ApplicationTestFactory.Create` uses `ManagedApplicationFixture` by default when the application entry point should own startup; pass an explicit fixture when a shared test context needs to customize its lifecycle. Because this lower-level factory returns the host directly, the caller still owns disposal. ```csharp using Codebelt.Extensions.Xunit.Hosting; diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationTestFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationTestFactory.md index 01cf124..641c4d0 100644 --- a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationTestFactory.md +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ApplicationTestFactory.md @@ -4,7 +4,7 @@ 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. +The test project references a worker application whose entry point registers `WorkerIdentity`. `ApplicationTestFactory` uses `ManagedApplicationFixture` by default, runs that application's real host setup, and exposes its services and environment through an owned test context so the test can verify application behavior without recreating `Program` configuration. Pass an explicit `IApplicationFixture` when the test needs a different lifecycle. ```csharp using Codebelt.Extensions.Xunit.Hosting; 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 deleted file mode 100644 index 59c530f..0000000 --- a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.BlockingManagedWebApplicationFixture%601.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -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` is an obsolete compatibility fixture that preserves the legacy blocking startup path for the current minor release; new tests should use `ManagedWebApplicationFixture` so the real application entry point owns startup. This compatibility type should be removed or changed in the next major release. - -```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.ManagedWebApplicationFixture%601.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebApplicationFixture%601.md index 4f1edf5..4926258 100644 --- a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebApplicationFixture%601.md +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebApplicationFixture%601.md @@ -4,7 +4,7 @@ example: - *content --- -Use `ManagedWebApplicationFixture` when an xUnit class fixture should exercise an ASP.NET Core application's real entry point and let that entry point start the in-memory server. Derive the test from `WebApplicationTest` and pass the fixture to its base constructor so the base class initializes the fixture through `ConfigureHost` before the test reads `Server`. This is an opt-in path for the current minor release. Fixture setup remains lazy; consuming the test host starts the deferred host, after which the test can create a client from the exposed `TestServer` and verify the application's endpoint behavior. The legacy blocking path is retained for compatibility until it can be removed or changed in the next major release. +Use `ManagedWebApplicationFixture` when an xUnit class fixture should exercise an ASP.NET Core application's real entry point and let that entry point start the in-memory server. Derive the test from `WebApplicationTest` and pass the fixture to its base constructor so the base class initializes the fixture through `ConfigureHost` before the test reads `Server`. Fixture setup remains lazy; consuming the test host starts the deferred host, after which the test can create a client from the exposed `TestServer` and verify the application's endpoint behavior. `WebApplicationTestFactory` uses this fixture by default. ```csharp using System.Threading.Tasks; diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationTestFactory.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationTestFactory.md index 57bc791..0d93104 100644 --- a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationTestFactory.md +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.AspNetCore.WebApplicationTestFactory.md @@ -4,7 +4,7 @@ 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. +The test project references a minimal ASP.NET Core application with a `/health` endpoint. `WebApplicationTestFactory` uses `ManagedWebApplicationFixture` by default, 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. Pass an explicit `IWebApplicationFixture` when the test needs a different lifecycle. ```csharp using System.Threading.Tasks; diff --git a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.BlockingManagedApplicationFixture%601.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.BlockingManagedApplicationFixture%601.md deleted file mode 100644 index 409276a..0000000 --- a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.BlockingManagedApplicationFixture%601.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -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` is an obsolete compatibility fixture that preserves the legacy blocking startup path for the current minor release; new tests should use `ManagedApplicationFixture` so the real application entry point owns startup. This compatibility type should be removed or changed in the next major release. - -```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.ManagedApplicationFixture%601.md b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedApplicationFixture%601.md index 2fab889..65cde41 100644 --- a/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedApplicationFixture%601.md +++ b/.docfx/api/types/Codebelt.Extensions.Xunit.Hosting.ManagedApplicationFixture%601.md @@ -4,7 +4,7 @@ example: - *content --- -Use `ManagedApplicationFixture` when an xUnit class fixture should exercise the application's real entry point and let that entry point start the host. Derive the test from `ApplicationTest` and pass the fixture to its base constructor so the base class initializes the fixture through `ConfigureHost` before the test reads `Host`. This is an opt-in path for the current minor release. Fixture setup remains lazy; accessing the test host starts the deferred host and surfaces startup failures at the point the test consumes it. The legacy blocking path is retained for compatibility until it can be removed or changed in the next major release. +Use `ManagedApplicationFixture` when an xUnit class fixture should exercise the application's real entry point and let that entry point start the host. Derive the test from `ApplicationTest` and pass the fixture to its base constructor so the base class initializes the fixture through `ConfigureHost` before the test reads `Host`. Fixture setup remains lazy; accessing the test host starts the deferred host and surfaces startup failures at the point the test consumes it. `ApplicationTestFactory` uses this fixture by default. ```csharp using Codebelt.Extensions.Xunit.Hosting; diff --git a/.editorconfig b/.editorconfig index 85dcc19..6d0e3c9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -82,11 +82,6 @@ dotnet_diagnostic.IDE0078.severity = none [*.{cs,vb}] dotnet_diagnostic.IDE0290.severity = none -# CA1200: Avoid using cref tags with a prefix -# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1200 -[*.{cs,vb}] -dotnet_diagnostic.CA1200.severity = none - # IDE0305: Use collection expression for fluent # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0305 [*.{cs,vb}] @@ -176,12 +171,6 @@ dotnet_diagnostic.IDE0032.severity = none # Order modifiers # https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0036 -# Excluded because of inconsistency with other analyzers +# Excluded becuase of inconsistency with other analyzers [*.{cs,vb}] dotnet_diagnostic.IDE0036.severity = none - -# Use 'System.Threading.Lock' -# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0330 -# Excluded while TFMs are less than net9.0 -[*.{cs,vb}] -dotnet_diagnostic.IDE0330.severity = none diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index fd26a57..a30dbed 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,52 +1,79 @@ -# Contributing to `Extensions for xUnit API by Codebelt` -When contributing to this repository, please first discuss the change you wish to make via issue, -email, or any other method with the owners of this repository before making a change. +# Contributing to Extensions for xUnit API by Codebelt -Please note we have a code of conduct, please follow it in all your interactions with the project. +This repository is part of the Codebelt .NET library estate. The instructions below describe the current checkout and its CI contract. Please keep changes focused and preserve the shared Codebelt build skeleton unless a deliberate policy change is being made. -## Code of Conduct -Please review our [code of conduct](CODE_OF_CONDUCT.md). +## Before you start -## Our Development Process -We use `trunk` based branching model that is aligned with todays DevSecOps practices. -All new features and/or fixes are merged into the `main` branch by creating a Pull Request. +- Read the repository `README.md` and open an issue before starting a non-trivial feature or behavioral change. +- Use an installed .NET SDK that can build the target frameworks listed below. This repository currently targets: **net10.0, net48, net9.0, netstandard2.0**. +- The solution is `Codebelt.Extensions.Xunit.slnx`. Central package versions are maintained in `Directory.Packages.props`. +- The shared build behavior is in `Directory.Build.props` and `Directory.Build.targets`; repository-specific TFMs, package references and metadata remain local to this library. -## Pull Requests -We actively welcome your pull requests. +## Repository shape -1. Fork the repo and create your branch from `main` -2. If you've added code that should be tested, add tests (DO follow [Microsoft Engineering Guidelines](https://github.com/dotnet/aspnetcore/wiki/Engineering-guidelines)) -3. Any changes or additions requires documentation in the form of documenting public members -4. Ensure that all existing as well as new test passes -5. Issue that pull request with a big and heartful thanks for contributing +- `src/` contains production projects. +- `test/` contains xUnit v3 test projects. +- `Codebelt.Extensions.Xunit.slnx` is the solution used for local development. +- `.github/workflows/ci-pipeline.yml` is the CI workflow and the authority for the test matrix. +- `testenvironments.json` declares the supported `WSL-Ubuntu` and `Docker-Ubuntu` test environments. -## Issues -We use GitHub issues to track public bugs. Please ensure your description is -clear and has sufficient instructions to be able to reproduce the issue. +## Build + +Restore and build the solution from the repository root: + +```powershell +dotnet restore "Codebelt.Extensions.Xunit.slnx" +dotnet build "Codebelt.Extensions.Xunit.slnx" --configuration Release --no-restore +``` + +CI builds both Debug and Release configurations. A clean build should complete before opening a pull request. + +## Test + +Run tests one project at a time so a failing or hanging project is attributable. This mirrors the CI matrix; it does not silently turn skipped integration tests into passing tests. + +```powershell +$testProjects = Get-ChildItem test -Filter *.csproj -Recurse +foreach ($project in $testProjects) { + dotnet test $project.FullName --configuration Release --no-restore +} +``` + +The CI test plan currently runs **5** project(s) and excludes **0** project(s). The workflow also has an optional macOS test job. + +## Integration and infrastructure -## Coding Guidelines -* Please follow [Framework Design Guidelines](https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/) -* Please follow SOLID principles -* Please follow [Microsoft Engineering Guidelines](https://github.com/dotnet/aspnetcore/wiki/Engineering-guidelines) +- `WSL-Ubuntu` — WSL distribution `Ubuntu-24.04`. +- `Docker-Ubuntu` — Docker image `codebeltnet/ubuntu-testrunner:8-9-10-11`. -## Manifesto -As aspiring Software Craftsmen we are raising the bar of professional software development by practicing it and helping others learn the craft. +This repository has no repository-local `docker-compose.yml` service dependency in the current checkout. +Use the environments declared in `testenvironments.json` when you need the estate test runner. -Through this work we have come to value: +## Package and documentation -* Not only working software, -but also well-crafted software -* Not only responding to change, -but also steadily adding value -* Not only individuals and interactions, -but also a community of professionals -* Not only customer collaboration, -but also productive partnerships +Create packages using the same solution and Release configuration: -That is, in pursuit of the items on the left we have found the items on the right to be indispensable. +```powershell +dotnet pack "Codebelt.Extensions.Xunit.slnx" --configuration Release --no-restore +``` -[Manifesto for Software Craftsmanship](https://manifesto.softwarecraftsmanship.org/) is the originator of this text. +Package-specific release notes live under `.nuget//PackageReleaseNotes.txt` and package README files live beside them. `Directory.Build.targets` imports the release notes during packing. Public API changes also require XML documentation updates; DocFX documentation is built by the repository automation. + +## Pull requests + +1. Create or join an issue before substantial work, then fork the repository and create a branch from `main`. +2. Add or update focused tests and public API documentation where applicable. +3. Run restore, build, and the relevant per-project tests locally. +4. Keep the pull request small, explain the behavior change and validation performed, and wait for the CI checks to pass. + +## Issues + +Include the affected project, target framework, operating system, SDK version, exact command, expected result, actual result, and a minimal reproduction. Identify whether the behavior differs between local Windows/WSL, Docker-Ubuntu and GitHub Actions. + +## Coding guidelines + +Follow the existing style, the Framework Design Guidelines, the repository `.editorconfig`, and the shared Codebelt conventions. Do not make unrelated formatting or infrastructure changes in a feature pull request. ## License -By contributing to `Extensions for xUnit API by Codebelt`, you agree that your contributions will be licensed -under the MIT license. \ No newline at end of file + +By contributing to Extensions for xUnit API by Codebelt, you agree that your contributions will be licensed under the MIT license. diff --git a/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt index 0dc48b6..8e209da 100644 --- a/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.App/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 12.0.0 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED xunit framework suite from v3.2.2 to v4.0.0 and upgraded supporting dependencies: Codebelt.Bootstrapper packages to v5.2.1, Codebelt.Extensions.BenchmarkDotNet.Console to v1.3.3, and Microsoft.NET.Test.Sdk to v18.9.0 + Version: 11.2.1 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 4920aac..c2e7dcc 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/PackageReleaseNotes.txt @@ -1,3 +1,18 @@ +Version: 12.0.0 +Availability: .NET 10 and .NET 9 + +# ALM +- CHANGED xunit framework suite from v3.2.2 to v4.0.0 and upgraded supporting dependencies: Codebelt.Bootstrapper packages to v5.2.1, Codebelt.Extensions.BenchmarkDotNet.Console to v1.3.3, and Microsoft.NET.Test.Sdk to v18.9.0 + +# Breaking Changes +- REMOVED BlockingManagedWebApplicationFixture{TEntryPoint}; use ManagedWebApplicationFixture{TEntryPoint} instead + +# Improvements +- CHANGED WebApplicationTestFactory to use ManagedWebApplicationFixture{TEntryPoint} as its default fixture + +# Bug Fixes +- FIXED deferred host startup completion in managed fixtures by registering the ApplicationStarted callback before releasing the host, ensuring StartAsync waits on the correct completion signal + Version: 11.2.1 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 9808463..b66624b 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/README.md +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/README.md @@ -15,7 +15,7 @@ The `Codebelt.Extensions.Xunit.Hosting.AspNetCore` namespace contains types that `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. -For the current minor release, the existing factory and blocking fixture paths preserve their legacy startup behavior. Use `ManagedWebApplicationFixture` explicitly when the application's `Main` method should own startup and the deferred `TestServer` should start when the test host is consumed. `BlockingManagedWebApplicationFixture` remains available as an obsolete compatibility option until it can be removed or changed in the next major release. +`WebApplicationTestFactory` uses `ManagedWebApplicationFixture` by default, so the application's `Main` method owns startup and the deferred `TestServer` starts when the test host is consumed. Use `ManagedWebApplicationFixture` explicitly with `WebApplicationTest` when tests share the application context. For lower-level web-host tests that require synchronous startup, use the separate `BlockingManagedWebHostFixture`. 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 6aa6be6..7390a79 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt @@ -1,3 +1,18 @@ +Version: 12.0.0 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED xunit framework suite from v3.2.2 to v4.0.0 and upgraded supporting dependencies: Codebelt.Bootstrapper packages to v5.2.1, Codebelt.Extensions.BenchmarkDotNet.Console to v1.3.3, and Microsoft.NET.Test.Sdk to v18.9.0 + +# Breaking Changes +- REMOVED BlockingManagedApplicationFixture{TEntryPoint}; use ManagedApplicationFixture{TEntryPoint} instead + +# Improvements +- CHANGED ApplicationTestFactory to use ManagedApplicationFixture{TEntryPoint} as its default fixture + +# Bug Fixes +- FIXED deferred host startup completion in managed fixtures by registering the ApplicationStarted callback before releasing the host, ensuring StartAsync waits on the correct completion signal + Version: 11.2.1 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 02b5d12..3953423 100644 --- a/.nuget/Codebelt.Extensions.Xunit.Hosting/README.md +++ b/.nuget/Codebelt.Extensions.Xunit.Hosting/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` 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. For the current minor release, existing factory and blocking fixture paths preserve their legacy startup behavior; use `ManagedApplicationFixture` explicitly when the application's `Main` method should own startup and the deferred host should start when the test host is consumed. `BlockingManagedApplicationFixture` remains as an obsolete compatibility option until it can be removed or changed in the next major release. +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. The factory uses `ManagedApplicationFixture` by default, so the application's `Main` method owns startup and the deferred host starts when the test host is consumed. Use `ManagedApplicationFixture` explicitly with `ApplicationTest` when tests share the application context. More documentation available at our documentation site: diff --git a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt index ce20004..416936a 100644 --- a/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt +++ b/.nuget/Codebelt.Extensions.Xunit/PackageReleaseNotes.txt @@ -1,3 +1,9 @@ +Version: 12.0.0 +Availability: .NET 10, .NET 9 and .NET Standard 2.0 + +# ALM +- CHANGED xunit framework suite from v3.2.2 to v4.0.0 and upgraded supporting dependencies: Codebelt.Bootstrapper packages to v5.2.1, Codebelt.Extensions.BenchmarkDotNet.Console to v1.3.3, and Microsoft.NET.Test.Sdk to v18.9.0 + Version: 11.2.1 Availability: .NET 10, .NET 9 and .NET Standard 2.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4801550..1605dbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,43 @@ 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. +## [12.0.0] - 2026-08-24 + +This is a major release driven by the upgrade to xUnit v4.0.0, reflecting the decision to bump this library's major version in alignment with the xUnit framework's major release after careful consideration. The xUnit v4 upgrade introduces breaking changes to the testing framework that may require updates to existing test code. + +> [!WARNING] +> **xUnit v4 Breaking Changes:** This release upgrades to xUnit v4.0.0, which contains breaking changes to the xUnit testing framework. Before upgrading, please review the [xUnit v4.0.0 release notes](https://xunit.net/releases/v3/4.0.0) for details on required code updates, API removals, and migration guidance. Pay special attention to changes in assertion methods, fixture behavior, and test collection definitions. + +> [!IMPORTANT] +> **CI migration for xUnit v4 on .NET 10 and later:** CI users upgrading to this release must add a root `global.json` opting into [Microsoft.Testing.Platform](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-dotnet-test) and update VSTest-specific test, result, and coverage arguments to their MTP-native equivalents. Add [`Microsoft.Testing.Extensions.CodeCoverage`](https://learn.microsoft.com/en-us/dotnet/core/testing/microsoft-testing-platform-extensions-code-coverage) to test projects when collecting coverage through MTP. Repositories that remain on pre-v4 xUnit or do not opt into MTP continue using the existing CI path. + +### Added + +- Global opt-in configuration via `global.json` to enable Microsoft.Testing.Platform `dotnet test` experience for xUnit v4 on .NET 10 and later, +- `Microsoft.Testing.Extensions.CodeCoverage` dependency to support native TRX and coverage reporting through MTP, +- `.gitattributes` file for consistent line-ending handling and merge strategies across platforms to reduce merge conflicts. + +### Changed + +- Upgraded xunit.v3, xunit.v3.assert, xunit.v3.extensibility.core, and xunit.v3.runner.console from v3.2.2 to v4.0.0, +- Upgraded xunit.runner.visualstudio from v3.1.5 to v4.0.0 for Visual Studio test explorer integration, +- Upgraded Codebelt.Bootstrapper.Console, Codebelt.Bootstrapper.Web, and Codebelt.Bootstrapper.Worker from v5.2.0 to v5.2.1, +- Upgraded Codebelt.Extensions.BenchmarkDotNet.Console from v1.3.2 to v1.3.3, +- Upgraded Microsoft.NET.Test.Sdk from v18.8.1 to v18.9.0 for improved compatibility with xUnit v4, +- `ApplicationTestFactory` and `WebApplicationTestFactory` now default to `ManagedApplicationFixture` and `ManagedWebApplicationFixture` for entrypoint-owned deferred startup behavior, +- Consolidated Docker test environments to a unified ubuntu-testrunner:8-9-10-11 image for improved maintainability across .NET 8, 9, 10, and 11, +- Updated DocFX base image to nginx 1.31-alpine and bumped DocFX from v2.78.4 to v2.78.5, +- Removed obsolete analyzer rules (CA1200 for cref tag prefix usage and IDE0330 for System.Threading.Lock) from editor configuration that are no longer relevant. + +### Removed + +- `BlockingManagedApplicationFixture` and `BlockingManagedWebApplicationFixture`; migrate to `ManagedApplicationFixture` and `ManagedWebApplicationFixture` which are now the default fixtures in the factory methods. + +### Fixed + +- Deferred host startup completion in managed fixtures by registering the ApplicationStarted callback before releasing the host, ensuring StartAsync waits on the correct completion signal without introducing the legacy registration race, +- Typo in .editorconfig IDE0036 comment documentation. + ## [11.2.1] - 2026-08-12 This is a patch release providing dependency updates to the latest compatible versions across all supported target frameworks. @@ -489,7 +526,8 @@ This major release is first and foremost focused on ironing out any wrinkles tha -[Unreleased]: https://github.com/codebeltnet/xunit/compare/v11.2.1...HEAD +[Unreleased]: https://github.com/codebeltnet/xunit/compare/v12.0.0...HEAD +[12.0.0]: https://github.com/codebeltnet/xunit/compare/v11.2.1...v12.0.0 [11.2.1]: https://github.com/codebeltnet/xunit/compare/v11.2.0...v11.2.1 [11.2.0]: https://github.com/codebeltnet/xunit/compare/v11.1.2...v11.2.0 [11.1.2]: https://github.com/codebeltnet/xunit/compare/v11.1.1...v11.1.2 diff --git a/Directory.Build.props b/Directory.Build.props index c64d6d6..41a8181 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -88,6 +88,7 @@ + diff --git a/Directory.Packages.props b/Directory.Packages.props index 58e221c..11d986b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,25 +6,26 @@ - - - - + + + + - + + - - - - - + + + + + diff --git a/global.json b/global.json new file mode 100644 index 0000000..3140116 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "test": { + "runner": "Microsoft.Testing.Platform" + } +} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingManagedWebApplicationFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingManagedWebApplicationFixture.cs deleted file mode 100644 index cc425af..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/BlockingManagedWebApplicationFixture.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Codebelt.Extensions.Xunit.Hosting.AspNetCore.Internal; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore; - -/// -/// Provides a blocking managed implementation of the interface. -/// -/// A type in the entry point assembly of the application. -/// -/// -/// -/// Unlike the base managed web host fixtures, this fixture starts the resolved application host synchronously. -/// ASP.NET Core application entry point testing must expose a started after fixture initialization. -/// Use for entrypoint-owned startup in new tests. -/// This compatibility fixture is retained for the current minor release and should be removed or changed in the next major release. -/// -[Obsolete("Use ManagedWebApplicationFixture so the application entry point owns host startup. This compatibility fixture will be removed or changed in the next major release.")] -public class BlockingManagedWebApplicationFixture : HostFixture, IWebApplicationFixture where TEntryPoint : class -{ - /// - /// Initializes a new instance of the class. - /// - public BlockingManagedWebApplicationFixture() - { - AsyncHostRunnerCallback = (host, _) => - { - host.Start(); - return Task.CompletedTask; - }; - } - - /// - /// Creates and configures the of this instance. - /// - /// The object that inherits from . - /// was added to support those cases where the caller is required in the host configuration. - /// - /// is null. - /// - /// - /// is not assignable from . - /// - public virtual void ConfigureHost(Test hostTest) - { - ArgumentNullException.ThrowIfNull(hostTest); - if (!HasTypes(hostTest.GetType(), typeof(WebApplicationTest<,>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(WebApplicationTest<,>), $"{nameof(hostTest)} is not assignable from WebApplicationTest."); } - if (this.HasValidState()) { return; } - - Host = WebApplicationHostFactory.Create(ConfigureWebHostCallback); - Server = Host.GetTestServer(); - Configuration = Host.Services.GetRequiredService(); - Environment = Host.Services.GetRequiredService(); - - ConfigureCallback(Configuration, Environment); - - AsyncHostRunnerCallback(Host, CancellationToken.None); - } - - /// - /// Gets or sets the delegate that provides a way to override the before the application is built. - /// - /// The delegate that provides a way to override the . - public Action ConfigureWebHostCallback { get; set; } - - /// - /// Gets the initialized by this instance. - /// - /// The initialized by this instance. - public TestServer Server { get; protected set; } -} diff --git a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebApplicationTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebApplicationTestFactory.cs index 1dbe47c..4ef00f0 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebApplicationTestFactory.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting.AspNetCore/WebApplicationTestFactory.cs @@ -17,16 +17,14 @@ public static class WebApplicationTestFactory /// /// A type in the entry point assembly of the application. /// The which may be configured. - /// An optional implementation to use instead of the default instance. + /// An optional implementation to use instead of the default instance. /// An instance of an implementation. /// - /// Passing a opts this call into entrypoint-owned deferred startup. Omitting preserves the blocking compatibility path for the current minor release; that default should be removed or changed in the next major release. + /// When is omitted, the factory uses so the application entry point owns deferred host startup. Pass another implementation when the test requires a different lifecycle. /// public static IHostTest Create(Action webHostSetup = null, IWebApplicationFixture hostFixture = null) where TEntryPoint : class { - // Minor-release compatibility: keep the historical blocking default while allowing callers to opt in by passing ManagedWebApplicationFixture. - // Major release: remove or change this default when the compatibility fixture is retired. Hint: ManagedWebApplicationFixture - return new Internal.WebApplicationTest(webHostSetup, hostFixture ?? new BlockingManagedWebApplicationFixture()); + return new Internal.WebApplicationTest(webHostSetup, hostFixture ?? new ManagedWebApplicationFixture()); } /// @@ -35,7 +33,7 @@ public static IHostTest Create(Action webHostSetup /// A type in the entry point assembly of the application. /// The which may be configured. /// The function delegate that creates a from the . Default is a GET request to the root URL ("/"). - /// An optional implementation to use instead of the default instance. + /// An optional implementation to use instead of the default instance. /// A that represents the asynchronous operation. The task result contains the for the test server. public static async Task RunAsync(Action webHostSetup = null, Func> responseFactory = null, IWebApplicationFixture hostFixture = null) where TEntryPoint : class { diff --git a/src/Codebelt.Extensions.Xunit.Hosting/ApplicationHostFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting/ApplicationHostFactory.cs index d77e2a5..3948fc6 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/ApplicationHostFactory.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/ApplicationHostFactory.cs @@ -16,8 +16,7 @@ public static class ApplicationHostFactory /// The delegate that provides a way to override the before the application is built. /// A built instance. /// - /// For compatibility, applications that expose CreateHostBuilder(string[]) are built through that factory. Applications that do not expose the legacy factory use the deferred entry-point path. - /// The legacy path and its wrapper behavior are retained for the current minor release; only the managed application fixtures opt into entrypoint-owned deferred startup. The legacy path should be removed or changed in the next major release. + /// For compatibility, applications that expose CreateHostBuilder(string[]) are built through that factory. Applications that do not expose the legacy factory use the deferred entry-point path. Managed application fixtures use the deferred entry-point path so the application owns startup. /// /// /// The entry point assembly does not expose a supported application host. @@ -35,8 +34,7 @@ public static IHost Create(Action configureHost) wher /// A value indicating whether the entry point should be stopped after the host is built. /// A built instance. /// - /// For compatibility, applications that expose CreateHostBuilder(string[]) are built through that factory and the value is ignored for that path. - /// The legacy path and its wrapper behavior are retained for the current minor release; only the managed application fixtures opt into entrypoint-owned deferred startup. The legacy path should be removed or changed in the next major release. + /// For compatibility, applications that expose CreateHostBuilder(string[]) are built through that factory and the value is ignored for that path. Managed application fixtures use the deferred entry-point path so the application owns startup. /// /// /// The entry point assembly does not expose a supported application host. diff --git a/src/Codebelt.Extensions.Xunit.Hosting/ApplicationTestFactory.cs b/src/Codebelt.Extensions.Xunit.Hosting/ApplicationTestFactory.cs index 31894f5..59ba412 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/ApplicationTestFactory.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/ApplicationTestFactory.cs @@ -13,15 +13,13 @@ public static class ApplicationTestFactory /// /// A type in the entry point assembly of the application. /// The which may be configured. - /// An optional implementation to use instead of the default instance. + /// An optional implementation to use instead of the default instance. /// An instance of an implementation. /// - /// Passing a opts this call into entrypoint-owned deferred startup. Omitting preserves the blocking compatibility path for the current minor release; that default should be removed or changed in the next major release. + /// When is omitted, the factory uses so the application entry point owns deferred host startup. Pass another implementation when the test requires a different lifecycle. /// public static IHostTest Create(Action hostSetup = null, IApplicationFixture hostFixture = null) where TEntryPoint : class { - // Minor-release compatibility: keep the historical blocking default while allowing callers to opt in by passing ManagedApplicationFixture. - // Major release: remove or change this default when the compatibility fixture is retired. Hint: ManagedApplicationFixture - return new Internal.ApplicationTest(hostSetup, hostFixture ?? new BlockingManagedApplicationFixture()); + return new Internal.ApplicationTest(hostSetup, hostFixture ?? new ManagedApplicationFixture()); } } diff --git a/src/Codebelt.Extensions.Xunit.Hosting/BlockingManagedApplicationFixture.cs b/src/Codebelt.Extensions.Xunit.Hosting/BlockingManagedApplicationFixture.cs deleted file mode 100644 index df6124b..0000000 --- a/src/Codebelt.Extensions.Xunit.Hosting/BlockingManagedApplicationFixture.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Codebelt.Extensions.Xunit.Hosting; - -/// -/// Provides a blocking managed implementation of the interface. -/// -/// A type in the entry point assembly of the application. -/// -/// -/// -/// Unlike the base managed host fixtures, this fixture starts the resolved application host synchronously. -/// Application entry point testing must expose a fully started host after fixture initialization. -/// Use for entrypoint-owned startup in new tests. -/// This compatibility fixture is retained for the current minor release and should be removed or changed in the next major release. -/// -[Obsolete("Use ManagedApplicationFixture so the application entry point owns host startup. This compatibility fixture will be removed or changed in the next major release.")] -public class BlockingManagedApplicationFixture : HostFixture, IApplicationFixture where TEntryPoint : class -{ - /// - /// Initializes a new instance of the class. - /// - public BlockingManagedApplicationFixture() - { - AsyncHostRunnerCallback = (host, _) => - { - host.Start(); - return Task.CompletedTask; - }; - } - - /// - /// Creates and configures the of this instance. - /// - /// The object that inherits from . - /// was added to support those cases where the caller is required in the host configuration. - /// - /// is null. - /// - /// - /// is not assignable from . - /// - public virtual void ConfigureHost(Test hostTest) - { -#if NETSTANDARD2_0 - if (hostTest == null) { throw new ArgumentNullException(nameof(hostTest)); } -#else - ArgumentNullException.ThrowIfNull(hostTest); -#endif - if (!HasTypes(hostTest.GetType(), typeof(ApplicationTest<,>))) { throw new ArgumentOutOfRangeException(nameof(hostTest), typeof(ApplicationTest<,>), $"{nameof(hostTest)} is not assignable from ApplicationTest."); } - if (this.HasValidState()) { return; } - - Host = ApplicationHostFactory.Create(ConfigureHostCallback); - Configuration = Host.Services.GetRequiredService(); - Environment = Host.Services.GetRequiredService(); - - ConfigureCallback(Configuration, Environment); - - AsyncHostRunnerCallback(Host, CancellationToken.None); - } - - /// - /// Gets or sets the delegate that provides a way to override the before the application is built. - /// - /// The delegate that provides a way to override the . - public Action ConfigureHostCallback { get; set; } -} diff --git a/src/Codebelt.Extensions.Xunit.Hosting/Internal/DeferredHostBuilder.cs b/src/Codebelt.Extensions.Xunit.Hosting/Internal/DeferredHostBuilder.cs index 61db5c7..936d37b 100644 --- a/src/Codebelt.Extensions.Xunit.Hosting/Internal/DeferredHostBuilder.cs +++ b/src/Codebelt.Extensions.Xunit.Hosting/Internal/DeferredHostBuilder.cs @@ -41,10 +41,12 @@ public IHost Build() var capture = (ProgramHostFactoryResolver.HostCapture)_hostFactory(args.ToArray()); var host = capture.Host; + // Register before the entry point is released from HostBuilt so its later ApplicationStarted callbacks run before this completion signal. + var startedRegistration = capture.ApplicationLifetime.ApplicationStarted.Register(() => _hostStarted.TrySetResult(null)); // Preserve the legacy wrapper for ApplicationHostFactory fallback callers. Only managed application fixtures opt into the marker that HostTest uses for lazy startup. var deferredHost = _entrypointOwned - ? new EntrypointOwnedDeferredHost(host, _hostStarted, capture) - : new DeferredHost(host, _hostStarted, capture); + ? new EntrypointOwnedDeferredHost(host, _hostStarted, capture, startedRegistration) + : new DeferredHost(host, _hostStarted, capture, startedRegistration); if (!_entrypointOwned) { @@ -119,27 +121,29 @@ public void Dispose() private class DeferredHost : IHost, IAsyncDisposable { private readonly IHost _host; - private readonly IHostApplicationLifetime _applicationLifetime; private readonly ProgramHostFactoryResolver.HostCapture _capture; private readonly TaskCompletionSource _hostStarted; + private readonly CancellationTokenRegistration _startedRegistration; - public DeferredHost(IHost host, TaskCompletionSource hostStarted, ProgramHostFactoryResolver.HostCapture capture) + public DeferredHost(IHost host, TaskCompletionSource hostStarted, ProgramHostFactoryResolver.HostCapture capture, CancellationTokenRegistration startedRegistration) { _host = host; - _applicationLifetime = capture.ApplicationLifetime; _capture = capture; _hostStarted = hostStarted; + _startedRegistration = startedRegistration; } public IServiceProvider Services => _host.Services; public void Dispose() { + _startedRegistration.Dispose(); _host.Dispose(); } public async ValueTask DisposeAsync() { + _startedRegistration.Dispose(); if (_host is IAsyncDisposable disposable) { await disposable.DisposeAsync().ConfigureAwait(false); @@ -151,15 +155,7 @@ public async ValueTask DisposeAsync() public async Task StartAsync(CancellationToken cancellationToken = default) { - if (_hostStarted.Task.IsCompleted) - { - await _hostStarted.Task.ConfigureAwait(false); - return; - } - using var registration = cancellationToken.Register(() => _hostStarted.TrySetCanceled()); - using var startedRegistration = _applicationLifetime.ApplicationStarted.Register(() => _hostStarted.TrySetResult(null)); - await _hostStarted.Task.ConfigureAwait(false); } @@ -176,7 +172,7 @@ public void ReleaseEntrypoint() private sealed class EntrypointOwnedDeferredHost : DeferredHost, IDeferredHost { - public EntrypointOwnedDeferredHost(IHost host, TaskCompletionSource hostStarted, ProgramHostFactoryResolver.HostCapture capture) : base(host, hostStarted, capture) + public EntrypointOwnedDeferredHost(IHost host, TaskCompletionSource hostStarted, ProgramHostFactoryResolver.HostCapture capture, CancellationTokenRegistration startedRegistration) : base(host, hostStarted, capture, startedRegistration) { } } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/DeferredInvalidWebApplicationTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/DeferredInvalidWebApplicationTest.cs index 72d51e6..74e2cc4 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/DeferredInvalidWebApplicationTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/DeferredInvalidWebApplicationTest.cs @@ -2,9 +2,9 @@ namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore; -internal sealed class DeferredInvalidWebApplicationTest : WebApplicationTest> -{ - public DeferredInvalidWebApplicationTest(BlockingManagedWebApplicationFixture hostFixture) : base(true, hostFixture) +internal sealed class DeferredInvalidWebApplicationTest : WebApplicationTest> +{ + public DeferredInvalidWebApplicationTest(ManagedWebApplicationFixture hostFixture) : base(true, hostFixture) { } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/BlockingManagedWebApplicationFixtureTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/ManagedWebApplicationFixtureTest.cs similarity index 68% rename from test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/BlockingManagedWebApplicationFixtureTest.cs rename to test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/ManagedWebApplicationFixtureTest.cs index fd65d89..f743c9e 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/BlockingManagedWebApplicationFixtureTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/ManagedWebApplicationFixtureTest.cs @@ -1,42 +1,42 @@ -using System; -using Xunit; -using ModernProgram = Codebelt.Extensions.Xunit.Hosting.Program.App.Program; - -namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore; - -public class BlockingManagedWebApplicationFixtureTest : Test -{ - public BlockingManagedWebApplicationFixtureTest(ITestOutputHelper output) : base(output) - { - } - - [Fact] - public void ConfigureHost_ShouldThrowArgumentOutOfRangeException_WhenHostTestIsNotWebApplicationTest() - { - var fixture = new BlockingManagedWebApplicationFixture(); - - var ex = Assert.Throws(() => fixture.ConfigureHost(this)); - - Assert.Equal("hostTest", ex.ParamName); - } - - [Fact] - public void ConfigureHost_ShouldThrowInvalidOperationException_WhenEntryPointAssemblyHasNoHost() - { - var fixture = new BlockingManagedWebApplicationFixture(); - var test = new DeferredInvalidWebApplicationTest(fixture); - - fixture.ConfigureCallback = test.Configure; - fixture.ConfigureWebHostCallback = test.Configure; - - Assert.Throws(() => fixture.ConfigureHost(test)); - } - - [Fact] - public void HasValidState_ShouldReturnFalse_WhenFixtureIsUninitialized() - { - var fixture = new BlockingManagedWebApplicationFixture(); - - Assert.False(fixture.HasValidState()); - } -} +using System; +using Xunit; +using ModernProgram = Codebelt.Extensions.Xunit.Hosting.Program.App.Program; + +namespace Codebelt.Extensions.Xunit.Hosting.AspNetCore; + +public class ManagedWebApplicationFixtureTest : Test +{ + public ManagedWebApplicationFixtureTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void ConfigureHost_ShouldThrowArgumentOutOfRangeException_WhenHostTestIsNotWebApplicationTest() + { + var fixture = new ManagedWebApplicationFixture(); + + var ex = Assert.Throws(() => fixture.ConfigureHost(this)); + + Assert.Equal("hostTest", ex.ParamName); + } + + [Fact] + public void ConfigureHost_ShouldThrowInvalidOperationException_WhenEntryPointAssemblyHasNoHost() + { + var fixture = new ManagedWebApplicationFixture(); + var test = new DeferredInvalidWebApplicationTest(fixture); + + fixture.ConfigureCallback = test.Configure; + fixture.ConfigureWebHostCallback = test.Configure; + + Assert.Throws(() => fixture.ConfigureHost(test)); + } + + [Fact] + public void HasValidState_ShouldReturnFalse_WhenFixtureIsUninitialized() + { + var fixture = new ManagedWebApplicationFixture(); + + Assert.False(fixture.HasValidState()); + } +} diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/StartupValidationWebApplicationTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/StartupValidationWebApplicationTestTest.cs index d1a8f8a..dfbb424 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/StartupValidationWebApplicationTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/StartupValidationWebApplicationTestTest.cs @@ -36,22 +36,6 @@ public void ShouldPropagateStartupValidationFailure_WhenUsingManagedWebApplicati Assert.True(validation.Started); } - [Fact] - public void ShouldNotInvokeEntrypoint_WhenUsingBlockingManagedWebApplicationFixture() - { - var missing = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); - var state = new ClassicProgramState(); - var validation = new StartupValidationService(state, missing); - using var fixture = new BlockingManagedWebApplicationFixture(); - using var application = WebApplicationTestFactory.Create( - builder => ConfigureStartupValidation(builder, state, validation), - fixture); - - Assert.NotNull(application.Host.Services); - Assert.False(state.MainInvoked); - Assert.True(validation.Started); - } - private static void ConfigureStartupValidation(IWebHostBuilder builder, ClassicProgramState state, StartupValidationService validation) { builder.ConfigureLogging(logging => logging.ClearProviders()); diff --git a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/WebApplicationTestFactoryTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/WebApplicationTestFactoryTest.cs index 2e8a653..f51ff91 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/WebApplicationTestFactoryTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.AspNetCore.FunctionalTests/WebApplicationTestFactoryTest.cs @@ -49,7 +49,7 @@ public async Task Create_ShouldBootstrapApplication_WhenEntryPointUsesBootstrapp [Fact] public async Task Create_ShouldBootstrapApplication_WhenEntryPointUsesClassicProgram() { - using var application = WebApplicationTestFactory.Create(hostFixture: new ManagedWebApplicationFixture()); + using var application = WebApplicationTestFactory.Create(); using var client = application.Host.GetTestClient(); var response = await client.GetAsync("/").ConfigureAwait(false); @@ -106,20 +106,6 @@ public async Task Create_ShouldApplyWebHostConfiguration_WhenWebHostSetupIsProvi Assert.Equal("Custom service from WebApplicationTestFactory", serviceBody); } - [Fact] - public async Task Create_ShouldSupportExplicitBlockingFixture() - { - using var application = WebApplicationTestFactory.Create(hostFixture: new BlockingManagedWebApplicationFixture()); - using var client = application.Host.GetTestClient(); - - using var response = await client.GetAsync("/").ConfigureAwait(false); - var body = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - - Assert.True(response.IsSuccessStatusCode); - Assert.Equal("Classic Program", body); - Assert.False(application.Host.Services.GetRequiredService().MainInvoked); - } - [Fact] public async Task RunAsync_ShouldReturnResponse_WhenEntryPointUsesModernProgramPattern() { diff --git a/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/ApplicationTestFactoryTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/ApplicationTestFactoryTest.cs index 08bb557..d7c5329 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/ApplicationTestFactoryTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/ApplicationTestFactoryTest.cs @@ -51,9 +51,12 @@ public void Create_ShouldStartEntrypoint_WhenUsingManagedApplicationFixture() public void Create_ShouldBootstrapApplication_WhenEntryPointUsesMinimalConsoleProgram() { using var application = ApplicationTestFactory.Create(); + var state = application.Host.Services.GetRequiredService(); Assert.Equal("Development", application.Environment.EnvironmentName); Assert.NotNull(application.Host); + Assert.True(state.MainInvoked); + Assert.True(state.EntrypointStarted); } [Fact] @@ -97,17 +100,6 @@ public void Create_ShouldApplyHostConfiguration_WhenHostSetupIsProvided() Assert.Equal("Configured from ApplicationTestFactory", application.Configuration["Factory:Message"]); } - [Fact] - public void Create_ShouldSupportExplicitBlockingFixture() - { - using var application = ApplicationTestFactory.Create(hostFixture: new BlockingManagedApplicationFixture()); - var state = application.Host.Services.GetRequiredService(); - - Assert.NotNull(application.Host); - Assert.True(state.MainInvoked); - Assert.False(state.EntrypointStarted); - } - [Fact] public void Create_CallerTypeShouldHaveDeclaringTypeOfApplicationTestFactoryTest() { diff --git a/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/BootstrapperConsoleApplicationTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/BootstrapperConsoleApplicationTestTest.cs index 61d0f2d..e151ab2 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/BootstrapperConsoleApplicationTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/BootstrapperConsoleApplicationTestTest.cs @@ -5,9 +5,9 @@ namespace Codebelt.Extensions.Xunit.Hosting; -public class BootstrapperConsoleApplicationTestTest : ApplicationTest> +public class BootstrapperConsoleApplicationTestTest : ApplicationTest> { - public BootstrapperConsoleApplicationTestTest(BlockingManagedApplicationFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) + public BootstrapperConsoleApplicationTestTest(ManagedApplicationFixture hostFixture, ITestOutputHelper output) : base(hostFixture, output) { } diff --git a/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/StartupValidationApplicationTestTest.cs b/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/StartupValidationApplicationTestTest.cs index 42705d7..3fb30f7 100644 --- a/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/StartupValidationApplicationTestTest.cs +++ b/test/Codebelt.Extensions.Xunit.Hosting.FunctionalTests/StartupValidationApplicationTestTest.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Logging; using Xunit; using BootstrapperMinimalConsoleProgram = Codebelt.Extensions.Xunit.Hosting.BootstrapperMinimalConsole.App.Program; -using BootstrapperMinimalConsoleState = Codebelt.Extensions.Xunit.Hosting.BootstrapperMinimalConsole.App.BootstrapperMinimalConsoleState; namespace Codebelt.Extensions.Xunit.Hosting; @@ -33,23 +32,6 @@ public void ShouldPropagateStartupValidationFailure_WhenUsingManagedApplicationF Assert.True(validation.Started); } - [Fact] - public void ShouldNotStartHostedServices_WhenUsingBlockingManagedApplicationFixture() - { - var missing = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); - var validation = new StartupValidationService(missing); - using var fixture = new BlockingManagedApplicationFixture(); - using var application = ApplicationTestFactory.Create( - builder => ConfigureStartupValidation(builder, validation), - fixture); - - var state = application.Host.Services.GetRequiredService(); - - Assert.True(state.MainInvoked); - Assert.False(state.EntrypointStarted); - Assert.False(validation.Started); - } - private static void ConfigureStartupValidation(IHostBuilder builder, StartupValidationService validation) { builder.ConfigureLogging(logging => logging.ClearProviders()); diff --git a/testenvironments.json b/testenvironments.json index e0b5a26..587b2ef 100644 --- a/testenvironments.json +++ b/testenvironments.json @@ -7,14 +7,9 @@ "wslDistribution": "Ubuntu-24.04" }, { - "name": "Docker-Ubuntu (net9)", + "name": "Docker-Ubuntu", "type": "docker", - "dockerImage": "codebeltnet/ubuntu-testrunner:9" - }, - { - "name": "Docker-Ubuntu (net10)", - "type": "docker", - "dockerImage": "codebeltnet/ubuntu-testrunner:10" + "dockerImage": "codebeltnet/ubuntu-testrunner:8-9-10-11" } ] }