Testing
Tempest Testing¶
Tempest provides a library for testing DynamoDB clients using DynamoDBLocal . It comes with two implementations:
- JVM: This is the preferred option, running a 
DynamoDBProxyServerbacked bysqlite4java, which is available on most platforms. - Docker: This runs dynamodb-local in a Docker container.
 
Feature matrix:
| Feature | tempest-testing-jvm | tempest-testing-docker | 
|---|---|---|
| Start up time | ~1s | ~10s | 
| Memory usage | Less | More | 
| Dependency | sqlite4java native library | Docker | 
JUnit 5 Integration¶
To use tempest-testing, first add this library as a test dependency:
For AWS SDK 1.x:
dependencies {
  testImplementation "app.cash.tempest:tempest-testing-jvm:1.10.0"
  testImplementation "app.cash.tempest:tempest-testing-junit5:1.10.0"
}
// Or
dependencies {
  testImplementation "app.cash.tempest:tempest-testing-docker:1.10.0"
  testImplementation "app.cash.tempest:tempest-testing-junit5:1.10.0"
}
For AWS SDK 2.x:
dependencies {
  testImplementation "app.cash.tempest:tempest2-testing-jvm:1.10.0"
  testImplementation "app.cash.tempest:tempest2-testing-junit5:1.10.0"
}
// Or
dependencies {
  testImplementation "app.cash.tempest:tempest2-testing-docker:1.10.0"
  testImplementation "app.cash.tempest:tempest2-testing-junit5:1.10.0"
}
Then in tests annotated with @org.junit.jupiter.api.Test, you may add TestDynamoDb as a test
extension. This extension spins up a
DynamoDB server. It shares the server across tests and keeps it running until the process exits. It
also manages test tables for you, recreating them before each test.
class MyTest {
  @RegisterExtension
  @JvmField
  val db = TestDynamoDb.Builder(JvmDynamoDbServer.Factory)
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem.TABLE_NAME, MusicItem::class.java))
      .build()
  private val musicTable by lazy { db.logicalDb<MusicDb>().music }
  @Test
  fun test() {
    val albumInfo = AlbumInfo(
        "ALBUM_1",
        "after hours - EP",
        "53 Thieves",
        LocalDate.of(2020, 2, 21),
        "Contemporary R&B"
    )
    // Talk to DynamoDB using Tempest's API.
    musicTable.albumInfo.save(albumInfo)
  }
  @Test
  fun anotherTest() {
    // Talk to DynamoDB using the AWS SDK.
    val result = db.dynamoDb.describeTable(
            DescribeTableRequest.builder().tableName(MusicItem.TABLE_NAME).build()
    )
    // Do something with the result...
  }
}
class MyTest {
  @RegisterExtension
  TestDynamoDb db = new TestDynamoDb.Builder(JvmDynamoDbServer.Factory.INSTANCE)
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem.TABLE_NAME, MusicItem.class))
      .build();
  MusicTable musicTable;
  @BeforeEach
  public void setup() {
    musicTable = db.logicalDb(MusicDb.class).music();
  }
  @Test
  public void test() {
    AlbumInfo albumInfo = new AlbumInfo(
        "ALBUM_1",
        "after hours - EP",
        "53 Thieves",
        LocalDate.of(2020, 2, 21),
        "Contemporary R&B"
    );
    // Talk to DynamoDB using Tempest's API.
    musicTable.albumInfo().save(albumInfo);
  }
  @Test
  public void anotherTest() {
    // Talk to DynamoDB using the AWS SDK.
    DescribeTableResponse result = db.getDynamoDb().describeTable(
            DescribeTableRequest.builder().tableName(MusicItem.TABLE_NAME).build()
    );
    // Do something with the result...
  }
}
class MyTest {
  @RegisterExtension
  @JvmField
  val db = TestDynamoDb.Builder(JvmDynamoDbServer.Factory)
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem::class.java))
      .build()
  private val musicTable by lazy { db.logicalDb<MusicDb>().music }
  @Test
  fun test() {
    val albumInfo = AlbumInfo(
        "ALBUM_1",
        "after hours - EP",
        "53 Thieves",
        LocalDate.of(2020, 2, 21),
        "Contemporary R&B"
    )
    // Talk to DynamoDB using Tempest's API.
    musicTable.albumInfo.save(albumInfo)
  }
  @Test
  fun anotherTest() {
    // Talk to DynamoDB using the AWS SDK.
    val result = db.dynamoDb.describeTable("music_items")
    // Do something with the result...
  }
}
class MyTest {
  @RegisterExtension
  TestDynamoDb db = new TestDynamoDb.Builder(JvmDynamoDbServer.Factory.INSTANCE)
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem.class))
      .build();
  MusicTable musicTable;
  @BeforeEach
  public void setup() {
    musicTable = db.logicalDb(MusicDb.class).music();
  }
  @Test
  public void test() {
    AlbumInfo albumInfo = new AlbumInfo(
        "ALBUM_1",
        "after hours - EP",
        "53 Thieves",
        LocalDate.of(2020, 2, 21),
        "Contemporary R&B"
    );
    // Talk to DynamoDB using Tempest's API.
    musicTable.albumInfo().save(albumInfo);
  }
  @Test
  public void anotherTest() {
    // Talk to DynamoDB using the AWS SDK.
    DescribeTableResult result = db.getDynamoDb().describeTable("music_items");
    // Do something with the result...
  }
}
To customize test tables, mutate the CreateTableRequest in a lambda.
fun testDb() = TestDynamoDb.Builder(JvmDynamoDbServer.Factory)
  .addTable(
    TestTable.create<MusicItem> { createTableRequest ->
      for (gsi in createTableRequest.globalSecondaryIndexes) {
        gsi.withProjection(Projection().withProjectionType(ProjectionType.ALL))
      }
      createTableRequest
    }
  )
  .build()
To use the Docker implementation, specify it in the builder.
fun testDb() = TestDynamoDb.Builder(DockerDynamoDbServer.Factory)
  .addTable(TestTable.create<MusicItem>())
  .build()
JUnit 4 Integration¶
To use tempest-testing, first add this library as a test dependency:
For AWS SDK 1.x:
dependencies {
  testImplementation "app.cash.tempest:tempest-testing-jvm:1.10.0"
  testImplementation "app.cash.tempest:tempest-testing-junit4:1.10.0"
}
// Or
dependencies {
  testImplementation "app.cash.tempest:tempest-testing-docker:1.10.0"
  testImplementation "app.cash.tempest:tempest-testing-junit4:1.10.0"
}
For AWS SDK 2.x:
dependencies {
  testImplementation "app.cash.tempest:tempest2-testing-jvm:1.10.0"
  testImplementation "app.cash.tempest:tempest2-testing-junit4:1.10.0"
}
// Or
dependencies {
  testImplementation "app.cash.tempest:tempest2-testing-docker:1.10.0"
  testImplementation "app.cash.tempest:tempest2-testing-junit4:1.10.0"
}
Then in tests annotated with @org.junit.Test, you may add TestDynamoDb as a
test rule. This rule spins up a
DynamoDB server. It shares the server across tests and keeps it running until the process exits. It
also manages test tables for you, recreating them before each test.
class MyTest {
  @get:Rule
  val db = TestDynamoDb.Builder(JvmDynamoDbServer.Factory)
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem.TABLE_NAME, MusicItem::class.java))
      .build()
  private val musicTable by lazy { db.logicalDb<MusicDb>().music }
  @Test
  fun test() {
    val albumInfo = AlbumInfo(
        "ALBUM_1",
        "after hours - EP",
        "53 Thieves",
        LocalDate.of(2020, 2, 21),
        "Contemporary R&B"
    )
    // Talk to DynamoDB using Tempest's API.
    musicTable.albumInfo.save(albumInfo)
  }
  @Test
  fun anotherTest() {
    // Talk to DynamoDB using the AWS SDK.
    val result = db.dynamoDb.describeTable(
            DescribeTableRequest.builder().tableName(MusicItem.TABLE_NAME).build()
    )
    // Do something with the result...
  }
}
class MyTest {
  @Rule
  public TestDynamoDb db = new TestDynamoDb.Builder(JvmDynamoDbServer.Factory.INSTANCE)
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem.TABLE_NAME, MusicItem.class))
      .build();
  MusicTable musicTable;
  @Before
  public void setup() {
    musicTable = db.logicalDb(MusicDb.class).music();
  }
  @Test
  public void test() {
    AlbumInfo albumInfo = new AlbumInfo(
        "ALBUM_1",
        "after hours - EP",
        "53 Thieves",
        LocalDate.of(2020, 2, 21),
        "Contemporary R&B"
    );
    // Talk to DynamoDB using Tempest's API.
    musicTable.albumInfo().save(albumInfo);
  }
  @Test
  public void anotherTest() {
    // Talk to DynamoDB using the AWS SDK.
    DescribeTableResponse result = db.getDynamoDb().describeTable(
            DescribeTableRequest.builder().tableName(MusicItem.TABLE_NAME).build()
    );
    // Do something with the result...
  }
}
class MyTest {
  @get:Rule
  val db = TestDynamoDb.Builder(JvmDynamoDbServer.Factory)
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem::class.java))
      .build()
  private val musicTable by lazy { db.logicalDb<MusicDb>().music }
  @Test
  fun test() {
    val albumInfo = AlbumInfo(
        "ALBUM_1",
        "after hours - EP",
        "53 Thieves",
        LocalDate.of(2020, 2, 21),
        "Contemporary R&B"
    )
    // Talk to DynamoDB using Tempest's API.
    musicTable.albumInfo.save(albumInfo)
  }
  @Test
  fun anotherTest() {
    // Talk to DynamoDB using the AWS SDK.
    val result = db.dynamoDb.describeTable("music_items")
    // Do something with the result...
  }
}
class MyTest {
  @Rule
  public TestDynamoDb db = new TestDynamoDb.Builder(JvmDynamoDbServer.Factory.INSTANCE)
      // `MusicItem` is annotated with `@DynamoDBTable`. Tempest recreates this table before each test.
      .addTable(TestTable.create(MusicItem.class))
      .build();
  MusicTable musicTable;
  @Before
  public void setup() {
    musicTable = db.logicalDb(MusicDb.class).music();
  }
  @Test
  public void test() {
    AlbumInfo albumInfo = new AlbumInfo(
        "ALBUM_1",
        "after hours - EP",
        "53 Thieves",
        LocalDate.of(2020, 2, 21),
        "Contemporary R&B"
    );
    // Talk to DynamoDB using Tempest's API.
    musicTable.albumInfo().save(albumInfo);
  }
  @Test
  public void anotherTest() {
    // Talk to DynamoDB using the AWS SDK.
    DescribeTableResult result = db.getDynamoDb().describeTable("music_items");
    // Do something with the result...
  }
}
To customize test tables, mutate the CreateTableRequest in a lambda.
fun testDb() = TestDynamoDb.Builder(JvmDynamoDbServer.Factory)
  .addTable(
    TestTable.create<MusicItem> { createTableRequest ->
      for (gsi in createTableRequest.globalSecondaryIndexes) {
        gsi.withProjection(Projection().withProjectionType(ProjectionType.ALL))
      }
      createTableRequest
    }
  )
  .build()
To use the Docker implementation, specify it in the builder.
fun testDb() = TestDynamoDb.Builder(DockerDynamoDbServer.Factory)
  .addTable(TestTable.create<MusicItem>())
  .build()
Other Testing Frameworks¶
Tempest testing is compatible with other testing frameworks. You’ll need to write your own integration code. Feel free to reference the implementations above. Here is a simpler example:
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
// ...
class JUnit5TestDynamoDb(
  private val testTables: List<TestTable>,
) : BeforeEachCallback, AfterEachCallback {
  private val service = TestDynamoDbService.create(JvmDynamoDbServer.Factory, testTables, 8000)
  override fun beforeEach(context: ExtensionContext) {
    service.startAsync()
    service.awaitRunning()
  }
  override fun afterEach(context: ExtensionContext?) {
    service.stopAsync()
    service.awaitTerminated()
  }
}
Check out the code samples on Github:
- Music Library - SDK 1.x (.kt, .java)
 - Music Library - SDK 2.x (.kt, .java)
 - Testing - SDK 1.x - JUnit4 - JVM (.kt, .java)
 - Testing - SDK 1.x - JUnit4 - Docker (.kt, .java)
 - Testing - SDK 1.x - JUnit5 - JVM (.kt, .java)
 - Testing - SDK 1.x - JUnit5 - Docker (.kt, .java)
 - Testing - SDK 2.x - JUnit4 - JVM (.kt, .java)
 - Testing - SDK 2.x - JUnit4 - Docker (.kt, .java)
 - Testing - SDK 2.x - JUnit5 - JVM (.kt, .java)
 - Testing - SDK 2.x - JUnit5 - Docker (.kt, .java)