Overview
This repository provides a layered set of extensions for the xUnit v3 test platform. The recurring problem it solves is consistency: tests across a solution tend to wire up disposal, dependency injection, configuration, and ASP.NET Core hosting in slightly different ways, which makes them harder to read and maintain. The family fixes a single test base class and progressively layers Microsoft Dependency Injection and an in-memory ASP.NET Core pipeline on top of it, so the same patterns hold whether you are testing a small library or a web component.
Concepts
The packages form a stack. A foundation package defines the shared test shape, a hosting package adds a real IHost with dependency injection, and an ASP.NET Core package adds a request pipeline; an aggregate package bundles all three. The concepts below describe what each layer contributes and how they connect.
A shared test foundation
Every test in the family starts from the abstract Test base class in Codebelt.Extensions.Xunit. It wires the xUnit ITestOutputHelper into a protected TestOutput property, implements IDisposable, IAsyncDisposable, and IAsyncLifetime, and exposes the protected OnDisposeManagedResources, OnDisposeManagedResourcesAsync, and OnDisposeUnmanagedResources hooks with a guard that runs cleanup at most once. The higher layers do not reinvent this contract: HostTest<T> derives from Test, and WebHostTest<T> derives from HostTest<T>, so the disposal and output behavior is identical at every level.
Capturing what the system under test produces
A consistent theme is observing artifacts a component produces rather than asserting on mocks. InMemoryTestStore<T> in Codebelt.Extensions.Xunit is the building block, with Add, Count, Query, and the type-filtering QueryFor<TResult>. Codebelt.Extensions.Xunit.Hosting reuses it: AddXunitTestLogging registers a logging provider that records each XunitTestLoggerEntry in such a store, and LoggerExtensions.GetTestStore hands the store back so a test can assert on what a service logged through the normal ILogger<T> API. The same store type therefore underpins both ad hoc collectors and captured logs.
Pattern-based string assertions
For output that contains variable fragments, the static Test.Match method matches an expected string against an actual one with wildcard support, tuned through WildcardOptions for the group, single-character, and throw-on-no-match behavior. This lets assertions ignore parts such as timestamps or generated identifiers without resorting to manual substring logic, and it is available wherever the Test base class is, which is every layer of the stack.
Dependency injection with a real host
Codebelt.Extensions.Xunit.Hosting turns a test into a configured IHost. The HostTest<T> and MinimalHostTest<T> base classes follow the Startup and class-fixture conventions through an IGenericHostFixture, where you override ConfigureServices and read back Host, Configuration, and Environment. When a fixture class is overkill, HostTestFactory and MinimalHostTestFactory build the same host inline. The ManagedHostFixture family supplies content root, the Development environment, configuration sources, and scope validation, while the self-managed variants hand startup control back to the caller. This is where a service under test resolves from a real container instead of hand-assembled dependencies.
ASP.NET Core pipeline testing
Codebelt.Extensions.Xunit.Hosting.AspNetCore extends the hosting layer with Microsoft.AspNetCore.TestHost. WebHostTest<T> and MinimalWebHostTest<T> add an abstract ConfigureApplication(IApplicationBuilder) and expose the built pipeline through Application, while WebHostTestFactory.RunAsync and RunWithHostBuilderContextAsync create the host, obtain an HttpClient from the in-memory server, and return the HttpResponseMessage. The package also registers a FakeHttpContextAccessor through AddFakeHttpContextAccessor so context-dependent components resolve in isolation. Because it builds on HostTest<T>, everything from the dependency-injection layer remains available, including the captured-logging store.
Layered packages and progressive opt-in
The packages are designed to be adopted one layer at a time, each referencing the one below it. A library that needs only the Test base class can depend on Codebelt.Extensions.Xunit alone; adding dependency injection means moving to Codebelt.Extensions.Xunit.Hosting; testing a request pipeline means Codebelt.Extensions.Xunit.Hosting.AspNetCore. Codebelt.Extensions.Xunit.App is the convenience aggregate that references all three so a project mixing these concerns can take a single dependency.
Usage guidance
Pick the lowest layer that covers a test project's needs. Use Codebelt.Extensions.Xunit for plain unit tests, step up to Codebelt.Extensions.Xunit.Hosting when a system under test depends on the dependency-injection container or configuration, and use Codebelt.Extensions.Xunit.Hosting.AspNetCore only when you need a request pipeline and a TestServer. Within the hosting layers, prefer the factory methods for short, self-contained tests and the base-class plus fixture model when several tests share setup or rely on scoped lifetimes. Reach for the Codebelt.Extensions.Xunit.App aggregate when one project genuinely spans these concerns; if you install it in a project that only writes plain unit tests, it still brings in the hosting and ASP.NET Core references that project will not use.