Skip to content

Recipe Migrate From Dynamodb Local

Mark Lauter edited this page May 16, 2026 · 2 revisions

Recipe: migrating tests off a DynamoDB Local container

You have a test suite that points at DynamoDB Local — running in a Docker container, started by Testcontainers or a docker-compose sidecar — and you want to drop the container dependency. The change is small in the test code (swap one client construction) and substantial in the test infrastructure (the entire container lifecycle goes away).

The interesting wrinkle is what's actually different at the API level. DynamoDB Local emulates the AWS service exhaustively; DynamoDbLite covers the surface in API Parity and stops there. Most test suites need none of the gaps — but if yours uses DynamoDB Streams, Backup & Restore, or PartiQL, the migration ends there.

What you remove

The container lifecycle. Pre-migration, a typical fixture looks like:

public sealed class DynamoLocalFixture : IAsyncLifetime
{
    private IContainer container = null!;

    public IAmazonDynamoDB Client { get; private set; } = null!;

    public async ValueTask InitializeAsync()
    {
        container = new ContainerBuilder()
            .WithImage("amazon/dynamodb-local:latest")
            .WithPortBinding(8000, true)
            .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8000))
            .Build();

        await container.StartAsync();

        Client = new AmazonDynamoDBClient(
            new BasicAWSCredentials("dummy", "dummy"),
            new AmazonDynamoDBConfig
            {
                ServiceURL = $"http://localhost:{container.GetMappedPublicPort(8000)}",
            });
    }

    public async ValueTask DisposeAsync()
    {
        Client.Dispose();
        await container.DisposeAsync();
    }
}

Container start-up alone is typically 1–3 seconds per test class — the dominant cost in a fast test run.

What you replace it with

public sealed class DynamoFixture : IDisposable
{
    public IAmazonDynamoDB Client { get; }

    public DynamoFixture()
    {
        Client = new DynamoDbClient(new DynamoDbLiteOptions(
            $"Data Source=test_{Guid.NewGuid():N};Mode=Memory;Cache=Shared"));
    }

    public void Dispose() => Client.Dispose();
}

No container, no port mapping, no wait strategy, no credentials. The constructor returns synchronously; tests that previously waited for the container start instead start immediately.

What stays the same

  • Every IAmazonDynamoDB call site — CreateTableAsync, PutItemAsync, QueryAsync, BatchWriteItemAsync, TransactWriteItemsAsync, the rest. The interface is the same; the in-process implementation services the same surface.
  • DynamoDBContext works against DynamoDbClient unchanged — see Recipe: DynamoDBContext for tests.
  • All your POCO mapping attributes ([DynamoDBTable], [DynamoDBHashKey], etc.).
  • Production code is unchanged. Only the test fixture differs.

Behavioral differences to watch for

A handful of test patterns rely on DynamoDB Local behavior that DynamoDbLite does not match exactly. Search your test code for the following before assuming the migration is mechanical:

  • Tests that wait for TableStatus.ACTIVE. DynamoDbLite tables are ACTIVE immediately on creation. If your tests poll DescribeTableAsync until ACTIVE, the loop exits on the first iteration — the test still passes, but the loop is dead code worth removing.
  • Tests that rely on TTL-expired items remaining visible briefly. DynamoDB and DynamoDB Local delete TTL-expired items on a delay; DynamoDbLite filters them at read time. A test that puts a TTL-expired item and then reads it back expecting to see it will fail under DynamoDbLite.
  • Tests that use TotalSegments to speed up scans. DynamoDbLite honors the segment fields but applies them post-retrieval — partitioning works, parallelism does not. See FAQ.
  • Tests that exercise ConsumedCapacity or ScannedCount matching ProvisionedThroughput throttling. DynamoDbLite does not track capacity. Throughput numbers are stored but not enforced.
  • Tests that use DynamoDB Streams, Backup & Restore, Global Tables, Kinesis Streams, PartiQL, Resource Policies, or Contributor Insights. These features are out of scope for an in-process emulator and throw NotSupportedException. See the API Parity matrix for the full list.

The full behavioral diff list lives on the API Parity page.

Container parity testing

If you want to keep one slow path that runs the same tests against a real DynamoDB Local container — to detect parity drift introduced by future DynamoDbLite changes — the DynamoDbLite.Parity.Tests project shows the pattern: a parameterized test suite that runs against in-memory DynamoDbLite, file-based DynamoDbLite, and a Testcontainers-managed DynamoDB Local. Most consumer test suites do not need this — DynamoDbLite is the test target and the Parity.Tests project is the safety net.

Next steps

Clone this wiki locally