Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ summary: *content

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<TEntryPoint>` or its one-request `RunAsync<TEntryPoint>` convenience. Use `WebApplicationTest<TEntryPoint, TFixture>` with `BlockingManagedWebApplicationFixture<TEntryPoint>` 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.
For a focused endpoint or service-override test, start with `WebApplicationTestFactory.Create<TEntryPoint>` or its one-request `RunAsync<TEntryPoint>` convenience. Use `WebApplicationTest<TEntryPoint, TFixture>` with `ManagedWebApplicationFixture<TEntryPoint>` 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)]

Expand All @@ -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<TEntryPoint>`|Returns an owned `IHostTest` whose host exposes the application's `TestServer`, services, configuration, and environment.|
|Send one request to an existing application|`WebApplicationTestFactory.RunAsync<TEntryPoint>`|Combines application startup, `HttpClient` creation, request execution, and cleanup in one call.|
|Share an existing application across an xUnit test class|`WebApplicationTest<TEntryPoint, TFixture>` with `BlockingManagedWebApplicationFixture<TEntryPoint>`|Uses xUnit fixture lifetime while keeping the real application entry point and `TestServer`.|
|Share an existing application across an xUnit test class|`WebApplicationTest<TEntryPoint, TFixture>` with `ManagedWebApplicationFixture<TEntryPoint>`|Opt-in 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.|

Expand All @@ -39,11 +39,11 @@ ASP.NET Core host fixtures follow the same lifecycle naming convention as the ho

|Prefix|Convention|
|---|---|
|`Managed`|The fixture owns host creation, configuration, startup and disposal using the default host runner.|
|`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.|

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<TEntryPoint>` when testing an existing ASP.NET Core application entry point with `TestServer`.
For the current minor release, the existing `WebApplicationTestFactory` and blocking fixture paths preserve legacy startup behavior. Use `ManagedWebApplicationFixture<TEntryPoint>` explicitly when the real `Main` method should own startup; fixture setup remains lazy and test-host consumption starts the deferred host. `BlockingManagedWebApplicationFixture<TEntryPoint>` 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<TEntryPoint>` directly because this API is blocking by convention from its first release.

Expand Down
6 changes: 3 additions & 3 deletions .docfx/api/namespaces/Codebelt.Extensions.Xunit.Hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<TEntryPoint>`|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<TEntryPoint, TFixture>` with `BlockingManagedApplicationFixture<TEntryPoint>`|Moves application startup and disposal into xUnit's fixture lifecycle while retaining configuration and service access.|
|Share an existing application across an xUnit test class|`ApplicationTest<TEntryPoint, TFixture>` with `ManagedApplicationFixture<TEntryPoint>`|Opt-in entrypoint-owned startup through the new managed fixture 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.|
Expand All @@ -27,11 +27,11 @@ Host fixtures follow a lifecycle naming convention:

|Prefix|Convention|
|---|---|
|`Managed`|The fixture owns host creation, configuration, startup and disposal using the default host runner.|
|`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.|

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<TEntryPoint>` when testing a console, worker, or Generic Host application from an existing entry point.
For the current minor release, the existing `ApplicationTestFactory` and blocking fixture paths preserve legacy startup behavior. Use `ManagedApplicationFixture<TEntryPoint>` explicitly when the real `Main` method should own startup; fixture setup remains lazy and test-host consumption starts the deferred host. `BlockingManagedApplicationFixture<TEntryPoint>` remains available as an obsolete compatibility fixture and should be removed or changed in the next major release.

### Extension Members

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ 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.
The test project references a worker application's entry-point assembly. `ApplicationHostFactory.Create<TEntryPoint>` 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<TEntryPoint>` to `ApplicationTestFactory.Create<TEntryPoint>`; 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.

```csharp
using System.Threading.Tasks;
using Codebelt.Extensions.Xunit.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Expand All @@ -16,18 +15,15 @@ namespace WorkerApp.Tests;

public sealed class ApplicationHostFactoryExample
{
public async Task<string> StartWithTestIdentityAsync()
public string GetTestIdentity()
{
using IHost host = ApplicationHostFactory.Create<WorkerProgram>(builder =>
{
builder.ConfigureServices(services =>
services.AddSingleton(new WorkerIdentity("Test inventory worker")));
});

await host.StartAsync().ConfigureAwait(false);
var identity = host.Services.GetRequiredService<WorkerIdentity>();
await host.StopAsync().ConfigureAwait(false);

return identity.Name;
}
}
Expand All @@ -36,12 +32,14 @@ public sealed record WorkerIdentity(string Name);

public sealed class WorkerProgram
{
public static void Main(string[] args)
public static IHostBuilder CreateHostBuilder(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton(new WorkerIdentity("Inventory worker"));
return Host.CreateDefaultBuilder(args);
}

using var host = builder.Build();
public static void Main(string[] args)
{
using var host = CreateHostBuilder(args).Build();
host.Run();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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<TEntryPoint>` waits for startup before constructing the test class, so the test can create a client from `TestServer` and exercise the real request pipeline immediately.
The test project references a minimal ASP.NET Core application and shares its in-memory server through xUnit's class-fixture lifetime. `BlockingManagedWebApplicationFixture<TEntryPoint>` is an obsolete compatibility fixture that preserves the legacy blocking startup path for the current minor release; new tests should use `ManagedWebApplicationFixture<TEntryPoint>` 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
uid: Codebelt.Extensions.Xunit.Hosting.AspNetCore.ManagedWebApplicationFixture`1
example:
- *content
---

Use `ManagedWebApplicationFixture<TEntryPoint>` 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<TEntryPoint,T>` 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.

```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 : WebApplicationTest<CatalogProgram, ManagedWebApplicationFixture<CatalogProgram>>
{
public CatalogApiTest(ManagedWebApplicationFixture<CatalogProgram> fixture, ITestOutputHelper output)
: base(fixture, output)
{
}

[Fact]
public async Task HealthEndpoint_ReturnsApplicationState()
{
using var client = 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();
}
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ example:
- *content
---

The test project references a worker application's entry point and shares one bootstrapped host through xUnit's class-fixture lifetime. `BlockingManagedApplicationFixture<TEntryPoint>` waits until the host is ready before constructing the test class, so each test can immediately resolve services registered by the real application.
The test project references a worker application's entry point and shares one bootstrapped host through xUnit's class-fixture lifetime. `BlockingManagedApplicationFixture<TEntryPoint>` is an obsolete compatibility fixture that preserves the legacy blocking startup path for the current minor release; new tests should use `ManagedApplicationFixture<TEntryPoint>` 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
uid: Codebelt.Extensions.Xunit.Hosting.ManagedApplicationFixture`1
example:
- *content
---

Use `ManagedApplicationFixture<TEntryPoint>` 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<TEntryPoint,T>` 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.

```csharp
using Codebelt.Extensions.Xunit.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Xunit;

namespace InventoryWorker.Tests;

Comment thread
greptile-apps[bot] marked this conversation as resolved.
public sealed class InventoryWorkerTest : ApplicationTest<WorkerProgram, ManagedApplicationFixture<WorkerProgram>>
{
public InventoryWorkerTest(ManagedApplicationFixture<WorkerProgram> fixture, ITestOutputHelper output)
: base(fixture, output)
{
}

[Fact]
public void Host_ContainsApplicationService()
{
var identity = Host.Services.GetRequiredService<WorkerIdentity>();

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();
}
}
```
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
Version: Unreleased
Availability: .NET 10 and .NET 9

# New Features
- ADDED ManagedWebApplicationFixture{TEntryPoint} as an opt-in entrypoint-owned fixture for ASP.NET Core application tests

# Changed
- CHANGED ManagedWebApplicationFixture{TEntryPoint} to invoke supported Main methods and let the application entry point own web-host startup
- PRESERVED WebApplicationTestFactory and BlockingManagedWebApplicationFixture{TEntryPoint} startup behavior for the current minor release

# Deprecated
- DEPRECATED BlockingManagedWebApplicationFixture{TEntryPoint}; use ManagedWebApplicationFixture{TEntryPoint} for new entrypoint-owned tests. The compatibility fixture should be removed or changed in the next major release.

Version: 11.1.2
Availability: .NET 10 and .NET 9

Expand Down
2 changes: 2 additions & 0 deletions .nuget/Codebelt.Extensions.Xunit.Hosting.AspNetCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ 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.

`WebApplicationTestFactory.Create<TEntryPoint>` 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<TEntryPoint>](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<TEntryPoint>` explicitly when the application's `Main` method should own startup and the deferred `TestServer` should start when the test host is consumed. `BlockingManagedWebApplicationFixture<TEntryPoint>` remains available as an obsolete compatibility option until it can be removed or changed in the next major release.

More documentation available at our documentation site:

Expand Down
13 changes: 13 additions & 0 deletions .nuget/Codebelt.Extensions.Xunit.Hosting/PackageReleaseNotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
Version: Unreleased
Availability: .NET 10, .NET 9 and .NET Standard 2.0

# New Features
- ADDED ManagedApplicationFixture{TEntryPoint} as an opt-in entrypoint-owned fixture for application tests

# Changed
- CHANGED ManagedApplicationFixture{TEntryPoint} to invoke supported Main methods and let the application entry point own startup
- PRESERVED ApplicationTestFactory and BlockingManagedApplicationFixture{TEntryPoint} startup behavior for the current minor release

# Deprecated
- DEPRECATED BlockingManagedApplicationFixture{TEntryPoint}; use ManagedApplicationFixture{TEntryPoint} for new entrypoint-owned tests. The compatibility fixture should be removed or changed in the next major release.

Version: 11.1.2
Availability: .NET 10, .NET 9 and .NET Standard 2.0

Expand Down
Loading
Loading