Mockitoを使ったAWS SDKを使ったプログラムのテスト

AWS SDKを使っていろいろとAWSに対して処理を書くことが多いのですが、テストコードを書く際にAWSリソースとのやりとりの部分のテストコードを書くことにどうすれば良いかわからないことがあります。

今回は、AWS SDK for Java v2を使ったS3を操作するコードに対してMockitoを使ったモックによるテストコードを書いてみたのでその紹介をします。

以下のようなコードがあるとします。

public class MyS3Service {
  private final S3Client s3Client;

  public MyS3Service(S3Client s3Client) {
    this.s3Client = s3Client;
  }

  public void uploadData(String bucket, String key, byte[] data) {
    PutObjectRequest putObjectRequest = PutObjectRequest.builder()
            .bucket(bucket)
            .key(key)
            .build();
    s3Client.putObject(putObjectRequest, RequestBody.fromBytes(data));
  }

  public List<String> listAllBuckets() {
    ListBucketsResponse response = s3Client.listBuckets();
    List<Bucket> bucketList = response.buckets();
    return bucketList.stream().map(Bucket::name).collect(Collectors.toList());
  }
}

やっていることは単純で、データをS3バケットに入れたり、バケットのリストを取得することをやっています。

上記のコードに対してテストコードを実装します。

まずは必要なライブラリです。今回はGradleを使っているので、build.gradleにて以下のように書きます。

plugins {
    id 'java'
    id 'com.diffplug.spotless' version '7.0.2'
}

group = 'com.github.miyohide'
version = '1.0'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'software.amazon.awssdk:s3:2.30.0'

    testImplementation platform('org.junit:junit-bom:5.10.0')
    testImplementation 'org.junit.jupiter:junit-jupiter'
    testImplementation 'org.mockito:mockito-core:5.15.2'
    testImplementation 'org.mockito:mockito-junit-jupiter:5.15.2'
}

test {
    useJUnitPlatform()
}

ポイントは、mokito-coremockito-junit-jupitertestImplementationに追加することです。あとはJUnit5を使ったテストコードと同じ。

テストコードは以下のような感じになりました。

@ExtendWith(MockitoExtension.class)
class MyS3ServiceTest {
  @Mock
  private S3Client s3Client;

  @InjectMocks
  private MyS3Service myS3Service;

  @Test
  void uploadData() {
    String bucketName = "test-bucket";
    String key = "test-key";
    byte[] data = "test-data".getBytes();

    myS3Service.uploadData(bucketName, key, data);

    PutObjectRequest expect = PutObjectRequest.builder().bucket(bucketName).key(key).build();

    verify(s3Client).putObject(eq(expect), any(RequestBody.class));
  }

  @Test
  void listAllBuckets() {
    ListBucketsResponse listBucketsResponse = ListBucketsResponse.builder()
                    .buckets(Bucket.builder().name("test-bucket1").build())
                            .build();
    when(s3Client.listBuckets()).thenReturn(listBucketsResponse);
    List<String> actual = myS3Service.listAllBuckets();
    List<String> expected = Arrays.asList("test-bucket1");
    assertEquals(expected, actual);
  }
}

@MockS3Clientをモック化して、@InjectMocksでモック化したS3Clientを使うようにしています。あとは普通にテストコードを書くだけ。

Mockitoの使い方は公式サイトや解説ページを見ながら色々と実装してみました。

site.mockito.org

モックオブジェクトに対してメソッドが呼び出された時に戻り値を指定するwhenthenReturnの組み合わせやverifyeqanyなどの使い方を知ると色々とテストが書けるかなと思います。

今回は、簡単のためにS3を対象にしました。S3でしたら以前取り上げたLocalStackなどを使ってテストを実行することができるのでMockitoを使ってテストコードを書く必要性はあまりないのですが、中にはシミュレートしにくい状況もあるので、こういう手段も取れるという例として紹介しました。

ただ、Mockitoで定義するAPIの戻り値は書いた通りにしか動かないので、AWSの仕様とは異なる可能性が高いということは大前提として認識しておく必要があります。