4

I am new to DynamoDB and I was wondering if there is any kind of embedded DynamoDB fake server available as a maven dependency/plugin, in order to run end-to-end tests as part of the maven build. E.g. something like Achilles but for DynamoDB instead of Cassandra.

Worth mentioning that I found this project, but I found it to be too unstable and I was not able to get it to work.

Thank you for your help.

João Matos
  • 6,102
  • 5
  • 41
  • 76

2 Answers2

4

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.

João Matos
  • 6,102
  • 5
  • 41
  • 76
  • Relating to localstack step 3. As stated [here](https://github.com/localstack/localstack#readme) starting with version 0.11.0, all APIs are exposed via a single edge service, which is accessible on http://localhost:4566 by default. Thus we should use something like `endpointConfiguration = new AwsClientBuilder.EndpointConfiguration("http://localhost:" + localStackContainer.getMappedPort(4566), Regions.US_EAST_1.getName())` and then `AmazonDynamoDBClientBuilder.standard().withEndpointConfiguration(endpointConfiguration)` – Alexander Radchenko Oct 12 '20 at 15:00
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.

madhead
  • 31,729
  • 16
  • 153
  • 201
  • nice! Can you suggest some way to start/stop the docker image automatically resp. before/after each unit test? – João Matos Apr 18 '19 at 08:46
  • @JoãoMatos, if you're using CI/CD services like below, than the container will be started before the test job and stopped after it automatically. If you're doing that locally you can just do `docker run` / `docker stop` – madhead Apr 18 '19 at 11:28