When writing integration tests with Testcontainers for .NET, one of the most common problems is port conflicts.
Imagine you have multiple tests that spin up containers in parallel. If they all try to bind the same host port (e.g., 6379
for Redis or 80
for Nginx), you’ll quickly run into errors like:
Bind for 0.0.0.0:6379 failed: port is already allocated
The fix is surprisingly simple: let Docker assign a random host port instead of forcing a fixed one.
The Trick
When you expose a container port, you can tell Testcontainers to map it to a free random port on the host:
.WithPortBinding(internalPort, assignRandomHostPort: true)
This avoids conflicts and allows your tests to run in parallel without stepping on each other.
Minimal Example
Here’s a lightweight example using nginx:alpine
:
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
public class WebServerTestContainer : IAsyncDisposable
{
private readonly IContainer _container;
private readonly TaskCompletionSource<int> _portInitialized = new();
private readonly int _internalPort = 80;
public int Port => _portInitialized.Task.Result;
public WebServerTestContainer()
{
_container = new ContainerBuilder()
.WithImage("nginx:alpine")
.WithExposedPort(_internalPort)
.WithPortBinding(_internalPort, true) // <— random host port
.WithWaitStrategy(
Wait.ForUnixContainer()
.UntilHttpRequestIsSucceeded(r => r
.ForPort(_internalPort)
.ForPath("/")))
.Build();
}
public async Task StartAsync()
{
await _container.StartAsync();
_portInitialized.SetResult(_container.GetMappedPublicPort(_internalPort));
}
public async ValueTask DisposeAsync() => await _container.DisposeAsync();
}
Using It in a Test
public class WebServerTests : IAsyncLifetime
{
private readonly WebServerTestContainer _server = new();
public async Task InitializeAsync()
{
await _server.StartAsync();
}
public async Task DisposeAsync()
{
await _server.DisposeAsync();
}
[Fact]
public async Task ShouldRespond()
{
using var http = new HttpClient();
var response = await http.GetAsync($"http://localhost:{_server.Port}/");
Assert.True(response.IsSuccessStatusCode);
}
}
Why This Matters
- ✅ No more port conflicts
- ✅ Tests can safely run in parallel
- ✅ No need to hardcode host ports
This small trick makes your integration tests more reliable and parallelizable without changing much code.
Top comments (0)