Skip to content

Instantly share code, notes, and snippets.

@yetanotherchris
Last active February 4, 2024 14:00
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save yetanotherchris/fb50071bced8bf0849ecd2cbbc3e9dce to your computer and use it in GitHub Desktop.
Save yetanotherchris/fb50071bced8bf0849ecd2cbbc3e9dce to your computer and use it in GitHub Desktop.
In memory http server for C# unit and integration tests
public static Task BasicHttpServer(string url, string outputHtml)
{
return Task.Run(() =>
{
HttpListener listener = new HttpListener();
listener.Prefixes.Add(url);
listener.Start();
// GetContext method blocks while waiting for a request.
HttpListenerContext context = listener.GetContext();
HttpListenerResponse response = context.Response;
Stream stream = response.OutputStream;
var writer = new StreamWriter(stream);
writer.Write(outputHtml);
writer.Close();
});
}
//
// Example
//
[Test]
public void should_return_html()
{
// Arrange
string url = GetLocalhostAddress();
using(BasicHttpServer(url, "some html"))
{
var myhttpclient = new MyHttpClient(new Uri(url));
// Act
string actualContent = myhttpclient.Read();
// Assert
Assert.That(actualContent, Does.Contain("some html"));
}
}
private string GetLocalhostAddress()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return $"http://localhost:{port}/";
}
@Gooseman
Copy link

This will only definitely work for a single test with a given URL. The URL may not have been released before a subsequent test tries to create a new listener. One way to address that is to

  • Create the listener in a using statement so that it is disposed on exit, regardless of any errors that might occur. This ensures the release of the URL.
  • Pass in a cancellation token from a cancellation token source.
  • After writer.Close, add a short Task.Delay that takes the token. 1 second should be more than enough time for the receiver of the response to read the stream.
  • After the method under test returns, cancel the token using the cancellation token source. This exits the Task.Delay without having to wait longer than is necessary.

These steps ensure that the receiver has enough time to read the stream and that the listener is disposed as soon as possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment