Testing
dotnet add package Shiny.Net.HttpServer.Testingawait using var app = TestHttpServer.Create(server => server.MapGet("/ping", ctx => ctx.Response.WriteTextAsync("pong")));
Assert.Equal("pong", await app.Client.GetStringAsync("/ping"));No port to allocate, no listener to bind, no firewall prompt, and nothing left behind when a test fails half way through. The server does not even have to be started — nothing is bound, so there is nothing to start.
What is actually being tested
Section titled “What is actually being tested”Only the socket is replaced. The request goes through the real HTTP/1.1 parser, the real router, the
real middleware pipeline and the real response framing, and the client is
HttpClient — which means chunked bodies, keep-alive, content negotiation and connection reuse are
all exercised exactly as they are over TCP.
The seam is the one tunnelling already uses: the server has never known
what its bytes arrive on. SocketsHttpHandler.ConnectCallback hands the client one end of a pair of
pipes, and HttpServer.ServeAsync gets the other.
Registering services
Section titled “Registering services”The builder is the same one the app uses, so a test substitutes dependencies the ordinary way:
await using var app = TestHttpServer.Create( server => server.MapMyAppEndpoints(), builder => { builder.Services.AddSingleton<IClock>(new FrozenClock(new DateTime(2026, 8, 23))); builder.Services.AddSingleton<IThermostat, FakeThermostat>(); });
var reading = await app.Client.GetFromJsonAsync<Reading>("/api/readings/current");app.Services reaches into the container for anything the assertions need.
HideExceptionDetails is off, so a handler that throws produces a 500 whose body says what threw —
which is the only useful thing for a test.
Several callers
Section titled “Several callers”using var second = app.CreateClient(); // its own connections and its own cookiesEach client is a separate caller as far as the server is concerned, which is what a test of sessions, authentication or a WebSocket registry needs.
HTTP/2
Section titled “HTTP/2”await using var app = TestHttpServer.Create(configure, useHttp2: true);Prior knowledge rather than ALPN, since there is no TLS in memory to negotiate with — which is also
true of a tunnelled connection. AllowCleartext is turned on for you.
For a server built elsewhere
Section titled “For a server built elsewhere”var client = server.CreateInMemoryClient();var handler = server.CreateInMemoryHandler(); // for a client the test configures itselfvar app = TestHttpServer.For(server);

