Mock S3 for integration test
Tags: java, aws, s3, testing
Published: 2022-08-07 03:50:54

Although we can use S3 mock library (e.g. `adobe/S3Mock`), sometimes we just want to make thing simple.

The [full source code](#full-source-code) is put at the end of this page.

## Add 3rd party dependencies

Undertow is used as S3 mock server. AWS SDK v2 is used for S3 client.

```xml
<dependency>
    <groupId>io.undertow</groupId>
    <artifactId>undertow-core</artifactId>
    <version>2.2.17.Final</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
    <version>2.17.247</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>url-connection-client</artifactId>
    <version>2.17.247</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.9.0</version>
    <scope>test</scope>
</dependency>
```

## Create S3 mock server

First we create a `HttpHandler`. It will return the content of S3 object.

```java
static class S3HttpHandler implements HttpHandler {

    @Override
    public void handleRequest(HttpServerExchange exchange) throws Exception {
        if (exchange.isInIoThread()) {
            exchange.dispatch(this);
            return;
        }

        System.out.printf("Request method: %s%n", exchange.getRequestMethod());
        System.out.printf("Request path: %s%n", exchange.getRequestPath());

        try (BlockingHttpExchange blockingExchange = exchange.startBlocking();
             OutputStream outputStream = exchange.getOutputStream();
             OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
             BufferedWriter writer = new BufferedWriter(outputStreamWriter)) {

            writer.write("Hello world");
        }
    }

}
```

Then we start the mock server with our `HttpHandler`.

```java
final int S3_MOCK_SERVER_PORT = 9999;

Undertow s3Server = Undertow.builder()
    .addHttpListener(S3_MOCK_SERVER_PORT, "0.0.0.0")
    .setHandler(new S3HttpHandler())
    .build();
s3Server.start();
```

Don't forget to stop our server at the end.

```java
s3Server.stop();
```

## Setup S3 client

We need to create a `S3Client` with the service endpoint pointing to our S3 mock server.

```java
String serviceEndpoint = String.format("http://localhost:%d", S3_MOCK_SERVER_PORT);

try (S3Client s3Client = S3Client.builder()
    .endpointOverride(URI.create(serviceEndpoint))
    .httpClient(UrlConnectionHttpClient.builder().build())
    .build()) {

}
```

Send a request to the mock server.

```java
GetObjectRequest request = GetObjectRequest.builder()
    .bucket("my-bucket")
    .key("my-object")
    .build();

Path targetPath = tempDir.resolve("result");

s3Client.getObject(request, targetPath);

String objectContent = Files.readString(targetPath);
System.out.printf("Object content: %s%n", objectContent);
assertEquals("Hello world", objectContent);
```

## Full source code <a name="full-source-code"></a>

```java
public class S3IntegrationTest {

    @Test
    public void shouldReturnS3Object(@TempDir Path tempDir) throws Exception {
        final int S3_MOCK_SERVER_PORT = 9999;

        Undertow s3Server = Undertow.builder()
            .addHttpListener(S3_MOCK_SERVER_PORT, "0.0.0.0")
            .setHandler(new S3HttpHandler())
            .build();

        s3Server.start();

        String serviceEndpoint = String.format("http://localhost:%d", S3_MOCK_SERVER_PORT);

        try (S3Client s3Client = S3Client.builder()
            .endpointOverride(URI.create(serviceEndpoint))
            .httpClient(UrlConnectionHttpClient.builder().build())
            .build()) {

            GetObjectRequest request = GetObjectRequest.builder()
                .bucket("my-bucket")
                .key("my-object")
                .build();

            Path targetPath = tempDir.resolve("result");

            s3Client.getObject(request, targetPath);

            String objectContent = Files.readString(targetPath);
            System.out.printf("Object content: %s%n", objectContent);
            assertEquals("Hello world", objectContent);
        }

        s3Server.stop();
    }

    static class S3HttpHandler implements HttpHandler {

        @Override
        public void handleRequest(HttpServerExchange exchange) throws Exception {
            if (exchange.isInIoThread()) {
                exchange.dispatch(this);
                return;
            }

            System.out.printf("Request method: %s%n", exchange.getRequestMethod());
            System.out.printf("Request path: %s%n", exchange.getRequestPath());

            try (BlockingHttpExchange blockingExchange = exchange.startBlocking();
                 OutputStream outputStream = exchange.getOutputStream();
                 OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
                 BufferedWriter writer = new BufferedWriter(outputStreamWriter)) {

                writer.write("Hello world");
            }
        }

    }

}
```