1. General information
aws-junit5 is a collection of JUnit 5 extensions that can be used to inject clients for AWS service mocks provided by tools like localstack or DynamoDB Local in your tests. Both AWS Java SDK v 2.x and v 1.x are supported. Currently, these services are supported:
2. Dependency Metadata
Artifacts are deployed to Maven Central Repository.
-
Group ID:
me.madhead.aws-junit5
-
Version:
7.1.2
-
Artifacts:
-
dynamo-v1
Support for injection of
AmazonDynamoDB
,AmazonDynamoDBAsync
,AmazonDynamoDBStreams
andAmazonDynamoDBStreamsAsync
viaDynamoDB
extension. -
dynamo-v2
Support for injection of
DynamoDbClient
,DynamoDbAsyncClient
,DynamoDbStreamsClient
andDynamoDbStreamsAsyncClient
viaDynamoDB
extension. -
s3-v1
-
s3-v2
Support for injection of
S3Client
andS3AsyncClient
viaS3
extension. -
kinesis-v1
Support for injection of
AmazonKinesis
,AmazonKinesisAsync
,AmazonKinesisFirehose
andAmazonKinesisFirehoseAsync
viaKinesis
extension. -
kinesis-v2
Support for injection of
KinesisClient
,KinesisAsyncClient
,FirehoseClient
andFirehoseAsyncClient
viaKinesis
extension. -
sns-v1
Support for injection of
AmazonSNS
andAmazonSNSAsync
viaSNS
extension. -
sns-v2
Support for injection of
SnsClient
andSnsAsyncClient
viaSNS
extension. -
sqs-v1
Support for injection of
AmazonSQS
andAmazonSQSAsync
viaSQS
extension. -
sqs-v2
Support for injection of
SqsClient
andSqsAsyncClient
viaSQS
extension. -
ses-v1
Support for injection of
AmazonSimpleEmailService
andAmazonSimpleEmailServiceAsync
viaSES
extension. -
ses-v2
Support for injection of
SesClient
andSesAsyncClient
viaSES
extension. -
lambda-v1
Support for injection of
AWSLambda
andAWSLambdaAsync
viaLambda
extension. -
lambda-v2
Support for injection of
LambdaClient
andLambdaAsyncClient
viaLambda
extension.
-
2.1. Gradle
repositories {
// Add Maven Central Repository if you don't use it already
mavenCentral()
}
dependencies {
// Choose the dependencies you need
testImplementation("me.madhead.aws-junit5:dynamo-v1:7.1.2")
testImplementation("me.madhead.aws-junit5:dynamo-v2:7.1.2")
testImplementation("me.madhead.aws-junit5:s3-v1:7.1.2")
testImplementation("me.madhead.aws-junit5:s3-v2:7.1.2")
testImplementation("me.madhead.aws-junit5:kinesis-v1:7.1.2")
testImplementation("me.madhead.aws-junit5:kinesis-v2:7.1.2")
testImplementation("me.madhead.aws-junit5:sns-v1:7.1.2")
testImplementation("me.madhead.aws-junit5:sns-v2:7.1.2")
testImplementation("me.madhead.aws-junit5:sqs-v1:7.1.2")
testImplementation("me.madhead.aws-junit5:sqs-v2:7.1.2")
testImplementation("me.madhead.aws-junit5:ses-v1:7.1.2")
testImplementation("me.madhead.aws-junit5:ses-v2:7.1.2")
testImplementation("me.madhead.aws-junit5:lambda-v1:7.1.2")
testImplementation("me.madhead.aws-junit5:lambda-v2:7.1.2")
}
2.2. Maven
Specify the dependencies you need as usual:
<dependencies>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>dynamo-v1</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>dynamo-v2</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>s3-v1</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>s3-v2</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>kinesis-v1</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>kinesis-v2</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>sns-v1</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>sns-v2</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>sqs-v1</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>sqs-v2</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>ses-v1</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>ses-v2</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>lambda-v1</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>me.madhead.aws-junit5</groupId>
<artifactId>lambda-v2</artifactId>
<version>7.1.2</version>
</dependency>
</dependencies>
3. Usage
3.1. Providing essential parameters
In order to use an AWS service you basically need 4 essential parameters:
-
URL
-
Region
-
Access key
-
Secret key
Not all of them are required, for example DynamoDB Local allows empty keys. But you are not locked to "fake" AWS implementations in your tests, you can use real endpoints as well.
To provide these values you need to implement AWSEndpoint
.
Tests could take the values from the environment or system variables, following the twelve-factor principles:
public static class Endpoint implements AWSEndpoint {
@Override
public String url() {
return System.getenv("DYNAMODB_URL");
}
@Override
public String region() {
return System.getenv("DYNAMODB_REGION");
}
@Override
public String accessKey() {
return System.getenv("DYNAMODB_ACCESS_KEY");
}
@Override
public String secretKey() {
return System.getenv("DYNAMODB_SECRET_KEY");
}
}
3.2. Basic usage
Annotate your test classes, eligible for clients injections, with a corresponding extensions. For the list of supported extensions and clients refer to dependency metadata section.
Finally, put AWSClient
annotation on the fields to be injected.
@ExtendWith(DynamoDB.class)
class AmazonDynamoDBInjectionTest {
@AWSClient(
endpoint = Endpoint.class
)
private AmazonDynamoDB client;
@Test
void test() {
Assertions.assertNotNull(client);
Assertions.assertEquals(
Collections.singletonList("table"),
client.listTables().getTableNames().stream().sorted().collect(Collectors.toList())
);
}
}
3.3. Advanced configuration
Sometimes, you need extra configuration.
For example, when you need to tune HTTP(s) protocol to change timeouts or trust self-signed certificates.
@AWSAdvancedConfiguration
annotation can be used to provide them.
Client configuration differs in AWS Java SDK v 1.x and 2.x, so there are two annotations with this name, one per AWS Java SDK major version.
-
Allows you to configure
ClientConfiguration
by implementingClientConfigurationFactory
. -
Allows you to configure:
-
ClientOverrideConfiguration
by implementingClientOverrideConfigurationFactory
. -
SdkHttpClient
by implementingSdkHttpClientFactory
. -
SdkAsyncHttpClient
by implementingSdkAsyncHttpClientFactory
.
-
Here is how you can configure asynchronous Netty-based client to use HTTP 1.1 protocol and trust all certificates:
public class KinesisSdkAsyncHttpClientFactory implements SdkAsyncHttpClientFactory {
@Override
public SdkAsyncHttpClient create() {
return NettyNioAsyncHttpClient
.builder()
.protocol(Protocol.HTTP1_1)
.buildWithDefaults(
AttributeMap
.builder()
.put(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES, java.lang.Boolean.TRUE)
.build()
);
}
}
And then use it:
@AWSClient(
endpoint = Endpoint.class
)
@AWSAdvancedConfiguration(
sdkAsyncHttpClientFactory = KinesisSdkAsyncHttpClientFactory.class
)
private KinesisAsyncClient client;
3.4. [Bonus]: CI with GitHub
This projects itself has tests. It uses LocalStack to mock AWS services. Here is how it works.
First, you need to tell your CI server to start localstack whenever it runs tests.
GitHub uses services
keyword in a job description:
services:
localstack:
image: localstack/localstack
ports:
- 4566:4566
env:
SERVICES: dynamodb,dynamodbstreams,s3,kinesis,firehose,sqs,sns,ses,lambda
LocalStack will start services listed in the SERVICES
environment variable.
Before running the test, some seed data needs to be initialized. Here is how it may look like for S3:
- run: |
dynamo/seed/seed.sh
s3/seed/seed.sh
kinesis/seed/seed.sh
sns/seed/seed.sh
sqs/seed/seed.sh
ses/seed/seed.sh
lambda/seed/seed.sh
#!/usr/bin/env sh
set -x
aws --endpoint-url "${S3_URL}" s3api delete-bucket --bucket bucket || true
aws --endpoint-url "${S3_URL}" s3api create-bucket --bucket bucket
Everything is ready to be tested now:
@ExtendWith(S3.class)
class S3ClientInjectionTest {
@AWSClient(
endpoint = Endpoint.class
)
private S3Client client;
@Test
void test() {
Assertions.assertNotNull(client);
Assertions.assertEquals(
Collections.singletonList("bucket"),
client
.listBuckets()
.buckets()
.stream()
.map(Bucket::name)
.sorted()
.collect(Collectors.toList())
);
}
}
This way your tests are free of any initialization logic, you just get the resources you need prepared and injected for you.
Simply changing the Endpoint
implementation you can attach any AWS compatible service to your test or even use real ones.