I was trying to fake Amazon Web Services
using localstack
by running it with Junit5
and this is what I did.
You can find the code for this example here: localstack-example.
For this you will need docker. I installed docker
on my mac with brew
using this post as a guide: How to install Docker on MacOS by Robin Wieruch which basically consists on running the following:
brew update
brew install docker
brew install docker-machine
brew cask install virtualbox
That is all the stuff we need to install, then we need to create the virtual machine with virtualbox
which will be running docker
.
docker-machine create --driver virtualbox default
And we start the virtual machine like this:
docker-machine start default
Now we need to run this:
docker-machine env default
Which will output something like this:
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/jsedano/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell:
# eval $(docker-machine env)
We then run that last sentence to prepare our shell with the environment variables needed:
eval $(docker-machine env)
That is it for the docker
installation.
Now for localstack
we will use localstack-java-utils which already has a guide and some code we will use, but I had to add other stuff.
These are the dependencies needed:
<!-- for easy amazon dependencies management -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-bom</artifactId>
<version>1.11.923</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- I only used s3 for this example -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</dependency>
<!-- I had to put this one since I think localstack-utils needs it -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>
<!-- JUnit5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0-M1</version>
<scope>test</scope>
</dependency>
<!-- localstack -->
<dependency>
<groupId>cloud.localstack</groupId>
<artifactId>localstack-utils</artifactId>
<version>0.2.2</version>
<scope>test</scope>
</dependency>
</dependencies>
And this is our test class:
package dev.jsedano.example;
import cloud.localstack.awssdkv1.TestUtils;
import cloud.localstack.docker.LocalstackDockerExtension;
import cloud.localstack.docker.annotation.LocalstackDockerProperties;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.amazonaws.util.IOUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.io.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(LocalstackDockerExtension.class)
@LocalstackDockerProperties(services = {"s3"}, hostNameResolver = HostNameResolverImpl.class)
public class AppTest {
@Test
public void creatingBucketAndPuttingObject() throws IOException {
String bucketName = "abucket";
String fileName = "test.txt";
String fileContent = "a String";
AmazonS3 s3 = TestUtils.getClientS3();
s3.createBucket(bucketName); //creating bucket
InputStream inputStream = new ByteArrayInputStream(fileContent.getBytes());
//uploading file to bucket
s3.putObject(bucketName,fileName, inputStream, new ObjectMetadata());
//downloading file from bucket
S3Object s3Object = s3.getObject(bucketName, fileName);
S3ObjectInputStream s3ObjectInputStream = s3Object.getObjectContent();
//checking is the same as the info we uploaded
assertEquals(fileContent, IOUtils.toString(s3ObjectInputStream));
}
}
I was trying to run the test with mvn clean verify
without setting up the hostNameResolver
but it was failing with the following error:
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 34.445 s <<< FAILURE! - in dev.jsedano.example.AppTest
[ERROR] creatingBucketAndPuttingObject Time elapsed: 1.645 s <<< ERROR!
com.amazonaws.SdkClientException: Unable to execute HTTP request: Connect to localhost.localstack.cloud:4566 [localhost.localstack.cloud/127.0.0.1] failed: Connection refused (Connection refused)
at dev.jsedano.example.AppTest.creatingBucketAndPuttingObject(AppTest.java:29)
Caused by: org.apache.http.conn.HttpHostConnectException: Connect to localhost.localstack.cloud:4566 [localhost.localstack.cloud/127.0.0.1] failed: Connection refused (Connection refused)
at dev.jsedano.example.AppTest.creatingBucketAndPuttingObject(AppTest.java:29)
Caused by: java.net.ConnectException: Connection refused (Connection refused)
at dev.jsedano.example.AppTest.creatingBucketAndPuttingObject(AppTest.java:29)
So I did this, which admittedly seems a little hackish…
package dev.jsedano.example;
import cloud.localstack.docker.annotation.IHostNameResolver;
public class HostNameResolverImpl implements IHostNameResolver {
@Override
public String getHostName() {
String dockerHost = System.getenv("DOCKER_HOST");
return dockerHost.substring(dockerHost.lastIndexOf('/')+1, dockerHost.lastIndexOf(':'));
}
}
If you remember the output of docker-machine env default
there is an environment variable called DOCKER_HOST
which contains the ip address of the underlying docker running on the virtual machine, and we only need the ip part, that’s why we are using that substring.
There should be other ways to solve this, for example modifying the hosts
file, or mapping the address to localhost with something like nginx
.
After that this is the output of running
mvn clean verify
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
One of the things I couldn’t do was to run this test using IntelliJ
, there should be a way to set the environment variables (by telling IntelliJ to run eval $(docker-machine env)
).
Download the complete code from this example here localstack-example.
Top comments (0)