👾

Testcontainers の LocalStack を試してみる

2024/10/30に公開

はじめに

今回は、先日試した Testcontainers を活用して、LocalStack を使った AWS サービスのモックテストをやってみようと思います💪

具体的には、Amazon S3 のモック環境を構築し、実際の AWS 環境と同様の操作ができるテストコードの作成に取り組みます。

Testcontainers の LocalStack モジュール

https://testcontainers.com/modules/localstack/

Amazon S3 を操作するコード

まずは Amazon S3 を操作するコードを用意します。
今回は簡単のために以下のページをそのまま実装しました。
https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html

このサンプルコードのなかで今回は src/main/java/org/example/Handler.java にある createBucket メソッドを LocalStack を使ってテストしてみようと思います!

Testcontainers の LocalStack を使ったテストのサンプル

まずは pom.xml に LocalStack モジュールを追加します。
以下の内容を加えてください。

        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers</artifactId>
            <version>1.20.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>1.20.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>localstack</artifactId>
            <version>1.20.2</version>
            <scope>test</scope>
        </dependency>

次に src/test/java/org/example/HandlerTest.java を書いていきます。
テストコードとしては単純に Handler.createBucket() を実行して、バケットができたか確認しているだけです。

HandlerTest.java
package org.example;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;

import org.testcontainers.containers.localstack.LocalStackContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.services.s3.S3Client;
import org.testcontainers.utility.DockerImageName;

import java.net.URI;

import static org.junit.jupiter.api.Assertions.*;

@Testcontainers
class HandlerTest {
    @Container
    private static final LocalStackContainer localStack = new LocalStackContainer(
            DockerImageName.parse("localstack/localstack:3.8"))
            .withServices(LocalStackContainer.Service.S3);

    private static S3Client s3Client;

    @BeforeAll
    public static void setUpAll() throws Exception {
        s3Client = S3Client.builder()
                .credentialsProvider(StaticCredentialsProvider.create(
                        AwsBasicCredentials.create(localStack.getAccessKey(), localStack.getSecretKey())))
                .endpointOverride(URI.create(localStack.getEndpointOverride(LocalStackContainer.Service.S3).toString()))
                .forcePathStyle(true)
                .build();
    }

    @Test
    void createBucket() {
        Handler.createBucket(s3Client, "test-bucket");
        // Check if the bucket exists
        assertTrue(s3Client.listBuckets().buckets().stream().anyMatch(bucket -> bucket.name().equals("test-bucket")));
    }
}

部分的に解説していきます。

    @Container
    private static final LocalStackContainer localStack = new LocalStackContainer(
            DockerImageName.parse("localstack/localstack:3.8"))
            .withServices(LocalStackContainer.Service.S3);

LocalStackContainer 型の静的フィールド localStack を定義しています。
LocalStackContainer は、LocalStack インスタンスを管理するための Testcontainers クラスです。

@Container アノテーションは、このコンテナが Testcontainers によって管理されることを表します。
LocalStackContainer は、特定の Docker イメージ localstack/localstack:3.8 で初期化されており、このイメージには LocalStack サービスが含まれています。
withServices メソッドは、S3 サービスがこの LocalStack インスタンスで利用可能であることを指定するために呼び出されます。

    @BeforeAll
    public static void setUpAll() throws Exception {
        s3Client = S3Client.builder()
                .credentialsProvider(StaticCredentialsProvider.create(
                        AwsBasicCredentials.create(localStack.getAccessKey(), localStack.getSecretKey())))
                .endpointOverride(URI.create(localStack.getEndpointOverride(LocalStackContainer.Service.S3).toString()))
                .forcePathStyle(true)
                .build();
    }

テストで使用される S3Clientをセットアップします。
メソッドは、AWS SDK for Java を使用して S3Client インスタンスを構築することから始まります。

S3Client.builder() メソッドを呼び出してクライアントの設定を開始します。
credentialsProvider メソッドは、クライアントの認証情報を設定するために使用されます。StaticCredentialsProvider.create を使用して静的な AWS 認証情報を提供し、AwsBasicCredentials.create を使用して認証情報を作成します。

アクセスキーとシークレットキーは、localStack コンテナから localStack.getAccessKey()localStack.getSecretKey() を使用して取得できます。
次に、endpointOverride メソッドを呼び出して S3 サービスのエンドポイントを設定します。
これは、LocalStack によって提供されるローカルの AWS サービスに対してテストを実行するために必要です。
エンドポイントは、localStack.getEndpointOverride(LocalStackContainer.Service.S3).toString() を使用して取得され、ローカルの S3 サービスの URL を返します。

forcePathStyle(true) メソッドは、S3 クライアントが LocalStack を使用する際に必要なパススタイルアクセスを使用するように設定します。
最後に、build() メソッドを呼び出して S3Client インスタンスを作成します。

このセットアップにより、S3Client が LocalStack によって提供されるローカルの S3 サービスと適切に連携できるようになり、実際の AWS クラウドにアクセスすることなくテストを実行することが可能になります。

テストの実行結果

リポジトリはこちら
https://github.com/Mo3g4u/testcontainers-localstack-for-java

最後に

TestcontainersとLocalStackを使用することで、AWS環境の構築やテストが効率化され、コスト削減にもつながります。
特にS3などのサービスを模擬環境で手軽にテストできるため、本番環境に依存せずに安全かつ迅速な開発・検証が可能です。ぜひ今回の手法を活用し、モックテストを実践してみてください!

レスキューナウテックブログ

Discussion