JUnit 5 extensions for AWS
Tags: junit, testing, aws
Published: 2020-06-23 12:48:20

Do you test the code that uses AWS services? Do you use JUnit 5 in your tests? If you do both, I may have something useful for you.

[JUnit 5 extensions for AWS](https://gitlab.com/madhead/aws-junit5): a few JUnit 5 extensions that could be useful for testing AWS-related code. These extensions can be used to inject clients for AWS service mocks provided by tools like [localstack](https://github.com/localstack/localstack). Both AWS Java SDK v 2.x and v 1.x are supported.

You may know (or you may imagine, if you never tried) that testing code that works with AWS is not easy. Just mocking the clients is enough only for simple unit tests, it won’t help you detect some service-specific issues like this:

``` kotlin
val kinesis: KinesisClient = …

kinesis.putRecord {
    it.streamName("kinesis-stream")
    it.data(SdkBytes.fromString("payload", StandardCharsets.UTF_8))
}
```

What’s wrong with this code? If you mock the client, there are basically two outcomes: either it accepts your request or it fails. But the real AWS service client will always fail. Kinesis requires partition key:

``` kotlin
kinesis.putRecord {
    it.streamName("kinesis-stream")
    it.partitionKey("partition-key") // this line is crucial!
    it.data(SdkBytes.fromString("payload", StandardCharsets.UTF_8))
}
```

This is true for every other AWS services: DynamoDB’s `getItem` requires table name and key, S3 requires bucket and key for `putObject` and so on. And you'll miss mistakes like this by simply mocking the clients.

Here, at the level of integration testing, come tools like [localstack](https://github.com/localstack/localstack), [MinIO](https://min.io) or [DynamoDB Local](https://aws.amazon.com/about-aws/whats-new/2018/08/use-amazon-dynamodb-local-more-easily-with-the-new-docker-image). The idea is to mock an AWS service using a low-cost, disposable (in-memory) implementation and use it to test your code. Localstack already supports two dozen of AWS services and the list is growing\!

You only have to get the clients in your test classes, and [`aws-junit5`](https://gitlab.com/madhead/aws-junit5) will help you do that with a minimal effort:

``` kotlin
@ExtendWith(Kinesis::class)
class SomeTest {
    @AWSClient(endpoint = Endpoint::class)
    @AWSAdvancedConfiguration(sdkAsyncHttpClientFactory = HTTPConfiguration::class)
    private kinesis: KinesisAsyncClient

    @Test
    fun test() {
        kinesis.putRecord {
            it.streamName("kinesis-stream")
            it.partitionKey("partition-key")
            it.data(SdkBytes.fromString("payload", StandardCharsets.UTF_8))
        }
    }
}
```

Here, `Endpoint` provides some vital values, taking them, for example, from the environment:

``` kotlin
class Endpoint : AWSEndpoint {
    override fun url() = System.getenv("DYNAMODB_URL")

    override fun region() = System.getenv("DYNAMODB_REGION")

    override fun accessKey() = System.getenv("DYNAMODB_ACCESS_KEY")

    override fun secretKey() = System.getenv("DYNAMODB_SECRET_KEY")
}
```

`HTTPConfiguration` is a bit of an optional configuration, needed in specific cases. Like configuring HTTP protocol details:

``` kotlin
class HTTPConfiguration : SdkAsyncHttpClientFactory {
    override fun create() =
        NettyNioAsyncHttpClient
            .builder()
            .protocol(Protocol.HTTP1_1)
            .buildWithDefaults(
                AttributeMap
                    .builder()
                    .put(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES, java.lang.Boolean.TRUE)
                    .build()
            )
}
```

In most cases only `@AWSClient` with `endpoint` is needed. You can use [`aws-junit5`](https://gitlab.com/madhead/aws-junit5) to inject clients for DynamoDB, Kinesis, S3, SES, SNS and SQS.

Even if you use real AWS services for testing (i.e. you have a dedicated environment for tests) — you still can use these extensions to get the required clients.

Read more in the [user guide](https://madhead.gitlab.io/aws-junit5/asciidoc) and [Javadocs](https://madhead.gitlab.io/aws-junit5/javadoc). Submit [an issue](https://gitlab.com/madhead/aws-junit5/issues/new) in case of any questions, problems, proposals or requests.

And…

![Motto](https://dev-to-uploads.s3.amazonaws.com/i/xiaelvwndeyqn2o9gi1s.png)