MicroStack
↑↓ navigate ↵ select esc close
GitHub

Integration Testing

MicroStack is designed for integration testing with the AWS SDK for .NET. The recommended approach uses .NET Aspire to spin up MicroStack as a container — matching how you run in production.

Testcontainers

MicroStack works with generic Testcontainers APIs in any language. The container contract is:

  • container port 4566
  • HTTP readiness at /_microstack/health
  • dummy AWS credentials and an explicit signing region such as us-east-1
  • path-style addressing for S3
  • random host-port mappings; do not assume host port 4566

Runnable examples are available for .NET, Java, Go, Python, and TypeScript. Each starts an isolated generic container and verifies SQS and S3 using the language's AWS SDK.

Set MICROSTACK_TEST_IMAGE to test a local image:

docker build -t microstack:test .
export MICROSTACK_TEST_IMAGE=microstack:test

SQS queue URLs follow the incoming request's scheme, host, and mapped port, so they can be passed back to the SDK unchanged. If tests run inside another container, use an endpoint reachable on that container network rather than the host-published endpoint. A single advertised address cannot be reachable from every network.

The release Dockerfile currently publishes a Linux x64 image. Docker Desktop can run it from Windows and macOS hosts; ARM hosts require container emulation until the Dockerfile release pipeline publishes ARM64 as well.

Aspire-Based Testing

1. AppHost Setup

In your Aspire AppHost project, add MicroStack as a resource:

dotnet add package MicroStack.Aspire.Hosting
var builder = DistributedApplication.CreateBuilder(args);
var microstack = builder.AddMicroStack("microstack");
builder.AddProject<Projects.MyApi>("api")
.WithReference(microstack);
builder.Build().Run();

2. Test Project Setup

Add the Aspire testing package and the AWS SDK packages you need:

<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" Version="13.*" />
<PackageReference Include="AWSSDK.SQS" Version="4.*" />
<PackageReference Include="AWSSDK.S3" Version="4.*" />
<!-- Add more AWSSDK.* packages as needed -->
</ItemGroup>

3. Test Fixture

Use DistributedApplicationTestingBuilder to start MicroStack as a container and retrieve the connection string:

public sealed class MicroStackFixture : IAsyncLifetime
{
public DistributedApplication App { get; private set; } = null!;
public string ConnectionString { get; private set; } = null!;
public async Task InitializeAsync()
{
var builder = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MyAspireAppHost>();
App = await builder.BuildAsync();
await App.StartAsync();
ConnectionString = await App.GetConnectionStringAsync("microstack")
?? throw new InvalidOperationException("MicroStack connection string not found");
}
public async Task DisposeAsync()
{
await App.StopAsync();
await App.DisposeAsync();
}
}

4. Creating AWS SDK Clients

Use the connection string from the fixture to create AWS SDK clients:

private static AmazonSQSClient CreateSqsClient(MicroStackFixture fixture)
{
var config = new AmazonSQSConfig
{
ServiceURL = fixture.ConnectionString,
};
return new AmazonSQSClient(new BasicAWSCredentials("test", "test"), config);
}
private static AmazonS3Client CreateS3Client(MicroStackFixture fixture)
{
var config = new AmazonS3Config
{
ServiceURL = fixture.ConnectionString,
ForcePathStyle = true,
};
return new AmazonS3Client(new BasicAWSCredentials("test", "test"), config);
}

5. Example Test

public sealed class SqsTests : IClassFixture<MicroStackFixture>, IAsyncLifetime
{
private readonly MicroStackFixture _fixture;
private readonly AmazonSQSClient _sqs;
public SqsTests(MicroStackFixture fixture)
{
_fixture = fixture;
_sqs = CreateSqsClient(fixture);
}
public async Task InitializeAsync()
{
// Reset state between tests
using var http = new HttpClient { BaseAddress = new Uri(_fixture.ConnectionString) };
await http.PostAsync("/_microstack/reset", null);
}
public Task DisposeAsync()
{
_sqs.Dispose();
return Task.CompletedTask;
}
[Fact]
public async Task CreateQueueAndSendMessage()
{
var created = await _sqs.CreateQueueAsync("test-queue");
Assert.NotEmpty(created.QueueUrl);
await _sqs.SendMessageAsync(new SendMessageRequest
{
QueueUrl = created.QueueUrl,
MessageBody = "hello world",
});
var received = await _sqs.ReceiveMessageAsync(new ReceiveMessageRequest
{
QueueUrl = created.QueueUrl,
});
Assert.Single(received.Messages);
Assert.Equal("hello world", received.Messages[0].Body);
}
}

Aspire Resource Configuration

You can customize the MicroStack resource in your AppHost:

var microstack = builder.AddMicroStack("microstack")
.WithDataVolume() // persistent state across restarts
.WithServices("s3,sqs,dynamodb") // limit enabled services
.WithRegion("eu-west-1"); // set AWS region
Method Description
AddMicroStack(name) Adds a MicroStack container on the default port
AddMicroStack(name, port) Adds a MicroStack container on a specific host port
WithDataVolume() Attaches a named volume for persistent state
WithServices(services) Limits which AWS services are enabled
WithRegion(region) Sets the AWS region

WebApplicationFactory (In-Process)

For projects that don't use Aspire, you can run MicroStack in-process using WebApplicationFactory<Program>. This requires a project reference to MicroStack and some additional setup for AWS SDK v4 compatibility.

Add MicroStack as a project reference:

<ItemGroup>
<ProjectReference Include="..\..\src\MicroStack\MicroStack.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.*" />
<PackageReference Include="AWSSDK.SQS" Version="4.*" />
</ItemGroup>

Test Fixture

using Microsoft.AspNetCore.Mvc.Testing;
public sealed class MicroStackFixture : IDisposable
{
public WebApplicationFactory<Program> Factory { get; }
public HttpClient HttpClient { get; }
public MicroStackFixture()
{
Factory = new WebApplicationFactory<Program>();
HttpClient = Factory.CreateClient();
}
public void Dispose()
{
HttpClient.Dispose();
Factory.Dispose();
}
}

AWS SDK Client Setup

AWS SDK v4 requires a CanonicalizeUriHandler workaround for TestServer:

internal sealed class CanonicalizeUriHandler(HttpMessageHandler inner)
: DelegatingHandler(inner)
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri is not null)
{
request.RequestUri = new Uri(request.RequestUri.AbsoluteUri);
}
return base.SendAsync(request, cancellationToken);
}
}
internal sealed class FixedHttpClientFactory(HttpClient client)
: Amazon.Runtime.HttpClientFactory
{
public override HttpClient CreateHttpClient(IClientConfig clientConfig) => client;
public override bool DisposeHttpClientsAfterUse(IClientConfig clientConfig) => false;
public override bool UseSDKHttpClientCaching(IClientConfig clientConfig) => false;
}

Then create SDK clients:

var innerHandler = fixture.Factory.Server.CreateHandler();
var httpClient = new HttpClient(new CanonicalizeUriHandler(innerHandler))
{
BaseAddress = new Uri("http://localhost/"),
};
var config = new AmazonSQSConfig
{
RegionEndpoint = RegionEndpoint.USEast1,
ServiceURL = "http://localhost/",
HttpClientFactory = new FixedHttpClientFactory(httpClient),
};
var sqs = new AmazonSQSClient(new BasicAWSCredentials("test", "test"), config);

Multi-Tenancy in Tests

Use 12-digit AWS access key IDs to simulate separate accounts:

// Account A
var clientA = new AmazonSQSClient(
new BasicAWSCredentials("111111111111", "test"), config);
// Account B
var clientB = new AmazonSQSClient(
new BasicAWSCredentials("222222222222", "test"), config);

Each account has isolated state — resources created by Account A are not visible to Account B.