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 testedOnly 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 servicesThe 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 callersusing 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/2await 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 elsewherevar client = server.CreateInMemoryClient();var handler = server.CreateInMemoryHandler(); // for a client the test configures itselfvar app = TestHttpServer.For(server);TLS, client certificates, connection limits, MaxRequestsPerConnection, and
anything about how the OS frames bytes belong on a real ephemeral port. This repos own test suite
uses real sockets for exactly those and this harness for the rest.