Skip to content

Run a test's application on ASP.NET Core with [AspNetCoreHost] - #276

Merged
ipjohnson merged 1 commit into
mainfrom
aspnet-host
Sep 5, 2026
Merged

ipjohnson merged 1 commit into
mainfrom
aspnet-host

Conversation

@ipjohnson

Copy link
Copy Markdown
Owner

What

[AspNetCoreHost] on a [HardenedTest], a test class or the assembly runs the test's application inside the real ASP.NET Core pipeline, on Kestrel, on a loopback port the kernel picks: a WebApplication built the way Program.cs builds one, over the test's own container. Everything the test holds follows it the way it does under [KestrelHost], and what this host adds is the pipeline around Hardened: an IAspNetCoreTestComposition named in the attribute arranges the middleware the way Program.cs does, app.UseHardened() alone by default, and an unmatched path falls through to what the composition put behind Hardened and then to ASP.NET's own 404, which is the behaviour this host exists to show.

How

WebApplication builds its own container, so the attribute is also the runner's container builder (IServiceProviderBuilderAttribute, which both DependencyModules runners honour): it copies every descriptor of the test's populated collection into a WebApplicationBuilder, builds, and returns app.Services as the test's provider. The server, the test's mocks and the test's parameters then resolve from one container, which is what keeps [Mock] true over this host. The ASP.NET environment name is the Hardened one in scope, test by default.

Stopping: the host is a singleton in app.Services, so the runner's disposal of the provider reaches it before Kestrel's registration; the shared transport is closed, app.StopAsync runs within SocketHost.StopBound, then app.DisposeAsync. Disposing the application disposes the provider that is already being disposed, which is safe because the container's dispose is guarded against re-entry; the port-closed test holds it.

Held by

  • Hardened.Web.AspNetCore.Testing.Tests: the container the test resolves from is the application's own; a loopback port the kernel picked; the default composition serves through Hardened; an unmatched path is ASP.NET's own 404 with no body; a composition's middleware behind Hardened answers what Hardened passed on; the environment name is the Hardened one in scope; disposing the container closes the port; a composition that is not one is refused by name.
  • AspNetCoreHostTests in the web SUT's test project, [AspNetCoreHost] on the class: the request through the ASP.NET pipeline, a [Mock] behind a route, a Kiota client read by Returns<T>(), LastResponse, three parameters with three credentials, ASP.NET's own 404 for an unmatched path, the handler's exception not crossing the wire, and [PipelineHost] on one method getting it back.
  • AspNetCoreHostUnderNUnitTests in the NUnit project: the same on the other runner, whose ModuleTestCommand builds the container through the attribute the same way.

Notes

  • Stacked on the Kestrel host branch (test-hosts), which is stacked on Split the test runner out of Hardened.Shared.Testing #273.
  • Pack list 28 to 29; public API approved for the new assembly; not in coverage-baseline.json, which the gate reads as unmeasured.
  • A test project already carrying an IServiceProviderBuilderAttribute of its own conflicts with this attribute; the runner takes the narrowest. Nothing in this repository does.

🤖 Generated with Claude Code

The real pipeline is WebApplication's, and WebApplication builds its own
container, so the attribute is also the runner's container builder: it
copies the test's collection into a WebApplicationBuilder, builds, and
hands the runner app.Services. The server, the mocks and the test's
parameters resolve from one container. Not terminal: an unmatched path
falls through to what the composition put behind UseHardened() and then
to ASP.NET's own 404. IAspNetCoreTestComposition stands in for what
Program.cs puts around Hardened.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@ipjohnson
ipjohnson merged commit e91f182 into main Sep 5, 2026
2 checks passed
@ipjohnson
ipjohnson deleted the aspnet-host branch September 6, 2026 00:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant