DynamoDB fake server for integration testing

Solution 1:

I found two possible approaches, DynamoDBEmbedded and Localstack. Regarding the latter, as disclaimed in the website:

LocalStack provides an easy-to-use test/mocking framework for developing Cloud applications. It spins up a testing environment on your local machine that provides the same functionality and APIs as the real AWS cloud environment.

It mocks several aws services including dynamodb. Example:

1) In my pom.xml under dependencies:

<dependency>
   <groupId>cloud.localstack</groupId>
   <artifactId>localstack-utils</artifactId>
   <version>0.1.19</version>
   <scope>test</scope>
</dependency>

2) Add these headers to you unit test class:

@RunWith(LocalstackDockerTestRunner.class)
@LocalstackDockerProperties(randomizePorts = true, services = {"dynamodb"})

3) Make sure your application is pointing to localstack url, e.g. dynamo will wait for you at http://localhost:4569. More information here.


Regarding the former, i.e. DynamoDBEmbedded, we can do the following:

1) In pom.xml under dependencies:

<dependency>
   <groupId>com.amazonaws</groupId>
   <artifactId>DynamoDBLocal</artifactId>
   <version>1.11.477</version>
   <scope>test</scope>
</dependency>

2) Then in our unit test:

private AmazonDynamoDB amazonDynamoDB;

@Before
public void setup() throws Exception {

    amazonDynamoDB =  DynamoDBEmbedded.create().amazonDynamoDB();//dynamoDB.getAmazonDynamoDB();
    amazonDynamoDB.createTable(new CreateTableRequest()
            .withTableName(TABLE_NAME)
            .withKeySchema(new KeySchemaElement().withAttributeName(ITEM).withKeyType(KeyType.HASH))
            .withAttributeDefinitions(
                    new AttributeDefinition().withAttributeName(ITEM).withAttributeType(ScalarAttributeType.S))
            .withProvisionedThroughput(new ProvisionedThroughput(10L, 15L))
    );
}

And make sure that our DAOs use this amazonDynamoDB instance.

Solution 2:

In August 2018 Amazon announced new Docker image with Amazon DynamoDB Local onboard. It does not require downloading and running any JARs as well as adding using third-party OS-specific binaries (I'm talking about sqlite4java).

It is as simple as starting a Docker container before the tests:

docker run -p 8000:8000 amazon/dynamodb-local

You can do that manually for local development, as described above, or use it in your CI pipeline. Many CI services provide an ability to start additional containers during the pipeline that can provide dependencies for your tests. Here is an example for Gitlab CI/CD:

test:
  stage: test
  image: openjdk:8-alpine
  services:
    - name: amazon/dynamodb-local
      alias: dynamodb-local
  script:
    - DYNAMODB_LOCAL_URL=http://dynamodb-local:8000 ./gradlew clean test

Or Bitbucket Pipelines:

definitions:
  services:
    dynamodb-local:
      image: amazon/dynamodb-local
…
step:
  name: test
  image:
    name: openjdk:8-alpine
  services:
    - dynamodb-local
  script:
    - DYNAMODB_LOCAL_URL=http://localhost:8000 ./gradlew clean test

After you've started the container you can create a client pointing to it:

private AmazonDynamoDB createAmazonDynamoDB(final DynamoDBLocal configuration) {
    return AmazonDynamoDBClientBuilder
        .standard()
        .withEndpointConfiguration(
            new AwsClientBuilder.EndpointConfiguration(
                "http://localhost:8000",
                Regions.US_EAST_1.getName()
            )
        )
        .withCredentials(
            new AWSStaticCredentialsProvider(
                // DynamoDB Local works with any non-null credentials
                new BasicAWSCredentials("", "")
            )
        )
        .build();
}

And if you're using JUnit 5, it can be a good idea to use a JUnit 5 extensions for AWS that will inject the client in your tests (yes, I'm doing a self-promotion):

  1. Add a dependency on me.madhead.aws-junit5:dynamo-v1 (dynamo-v2 for AWS Java SDK v2.x client)

    pom.xml:

    <dependency>
        <groupId>me.madhead.aws-junit5</groupId>
        <artifactId>dynamo-v1</artifactId>
        <version>6.0.3</version>
        <scope>test</scope>
    </dependency>
    

    build.gradle

    dependencies {
        testImplementation("me.madhead.aws-junit5:dynamo-v1:6.0.3")
    }
    
  2. Use the extension in your tests:

    @ExtendWith(DynamoDB.class)
    class Test {
        @AWSClient(
            endpoint = Endpoint.class
        )
        private AmazonDynamoDB client;
    
        @Test
        void test() {
            client.listTables();
        }
    }
    

Read the full user guide, it's really short.