AWSのサービス（CognitoやSES等）をWireMockでモックする

# はじめに
ローカル開発環境下で、AWSの実際のサービスに接続せずモックで対応したいケースは多々あります。
AWSサービスのmockは巷にはあるのですが、それを使えるようにするための準備が結構大変そうです。
しかし、WireMockを利用すれば、手早く使用するサービスの機能に限定してMockすることが出来ます。
ここでは、WireMockを使用してAWSのサービスをMockする方法を紹介したいと思います。

# 使用するライブラリ
* [WireMock](https://wiremock.org/docs/standalone/java-jar/)

**前提条件**
上記ライブラリはjava環境が必要なため、Javaがインストールされている必要があります。

## WireMockとは
WireMockとは、WebAPIのシミュレーションを行えるツールです。

# やり方
## MockするためのAPI仕様を確認する

AWSの使用するサービスによりやり方が変わってくるかと思いますが、まずは、以下のサイトにより、APIの仕様を確認します。

https://docs.aws.amazon.com/

上記サイトを開き、"Product guides & references"の項の"All products"で使用するサービスを検索バーで入力します。

例えば、Cognitoを使用する場合
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863714/13756561-0a48-d8eb-fcab-01eb9755d835.png)

そして、Amazon Cognitoをクリックした先のページで、実際にMockしたいサービスのAPI Referenceをクリック
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863714/406c284d-8648-7d93-badb-b0ab17cf5439.png)

左メニューの"Action"から、実際にMockしたい処理を選択します。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863714/22a4fa4e-751f-b750-01ee-cd3f62dc03a4.png)

下記例は、AdminInitiateAuth を選択したケースです。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863714/52858ff3-0d81-ff42-4b43-2f768c905534.png)

この画面には、リクエストの仕様とレスポンスの仕様が記述されています。
モックするためには、このリクエスト仕様とレスポンス仕様が必要となります。

そして、画面下部に、"Sample Request"と"Sample Response"があります。
それを参考に、実際にMockするための設定を作成していくこととなります。
実際のページは、以下です。
https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminInitiateAuth.html

## CognitoサービスをMockする
WireMockで動かすための設定を実際に行います。

WireMockの使い方に関してはここでは特に触れません。
ここでは設定ファイルの内容の例を示します。

用意するものは２つです。
* スタブ用のJSONファイル　→　mappingsフォルダに格納
* レスポンスを記述したファイル　→　__filesフォルダに格納

CognitoサービスのAdminInitiateAuthを例にとります。
```json:スタブ用JSONファイル
{
    "request": {
        "method": "POST",
        "url": "/cognito-idp/",
        "headers": {
            "x-amz-target": {
                "equalTo": "AWSCognitoIdentityProviderService.AdminInitiateAuth",
                "caseInsensitive": true
            }
        }
    },
    "response": {
        "status": 200,
        "bodyFileName": "InitiateAuthResponse.json",
        "headers": {
          "Content-Type": "application/json"
        }
    }
}
```
```json:レスポンスを記述したファイル(InitiateAuthResponse.json)
{
   "AuthenticationResult": { 
      "AccessToken": "eyJraACCESSEXAMPLE...",
      "ExpiresIn": 3600,
      "IdToken": "eyJraIDEXAMPLE...",
      "RefreshToken": "eyJjREFRESHEXAMPLE...",
      "TokenType": "Bearer"
   },
   "ChallengeParameters": {}
}
```
上記例の場合は、レスポンスを記述したファイルの中身は、ほぼAPIリファレンスのサンプルのままです。

上記で、注意したい点は、Cognitoサービスのエンドポイントを、
``http://<WireMock起動のホスト>:<起動ポート番号>/cognito-idp``

としている点です。
WireMockは、ルートへのアクセスは403となってしまうため、上記のように、``/cognito-idp/`` のような記述を追記しています。
当然、接続元のエンドポイントも上記URLに変更する必要があります。（それに関しては後述）

API仕様には特段記述されていないのですが、Cognitoの場合は、ヘッダの``X-AMZ-TARGET``により、実施する処理の振り分けを行っております。
そこで、スタブ用JSONファイルの``headers``セクションで、``X-AMZ-TARGET``の値を定義することで、実際にアクセスされた場合にどのレスポンスを返すかを定義します。

AWSのAPI Reference の各アクションのExamplesセクションのSample Request を参考に、``X-AMZ-TARGET``の値を設定します。


レスポンスに関しては、同じく、AWSのAPI Referenceの各アクションのResponse SyntaxとSample Responseの項を参考に、ファイルを作成します。

WireMock起動時に、``--verbose``オプションを追記すると、コンソールにログが出力されます。
上記の設定にて実際に接続した場合のWireMockに出力された実行結果は、以下のようになります。
```
127.0.0.1 - POST /cognito-idp/

Host: [localhost:8111]
amz-sdk-invocation-id: [170e4d7b-dd71-429f-bd91-d4463ec3a87f]
amz-sdk-request: [attempt=1; max=4]
Authorization: [AWS4-HMAC-SHA256 Credential=accessKey/20240917/ap-northeast-1/cognito-idp/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target, Signature=a371a9460489cba63bf33e62a2d360661e4cdfc373e6f8ae34b2a39f93d3c9ed]
Content-Type: [application/x-amz-json-1.1]
User-Agent: [aws-sdk-java/2.27.3 Windows_Server_2019/10.0 OpenJDK_64-Bit_Server_VM/21.0.3+9-LTS Java/21.0.3 vendor/Eclipse_Adoptium io/sync http/Apache cfg/retry-mode/legacy cfg/auth-source#stat]
x-amz-content-sha256: [059e694820d507558a2850fa104161dc909b916d9f391a6d339cd5f1165e2735]
X-Amz-Date: [20240917T003045Z]
X-Amz-Target: [AWSCognitoIdentityProviderService.AdminInitiateAuth]
Content-Length: [179]
Connection: [keep-alive]
{"UserPoolId":"ap-northeast-1_123456789","ClientId":"1example23456789","AuthFlow":"ADMIN_NO_SRP_AUTH","AuthParameters":{"PASSWORD":"testpass","USERNAME":"testuserid"}}


Matched response definition:
{
  "status" : 200,
  "bodyFileName" : "InitiateAuthResponse.json",
  "headers" : {
    "Content-Type" : "application/json"
  }
}

Response:
HTTP/1.1 200
Content-Type: [application/json]
Matched-Stub-Id: [fe30d1bb-cc63-4ee4-9def-85894d661959]

```
上記のように、``--verbose``オプションにより、クライアントより送信されたリクエスト内容がコンソールに出力されます。

## SES V2サービスをMockする
SESの場合も同じく、ますは、APIリファレンスを確認します。
SESのV2のAPIリファレンスを参考にしたい場合は、Cognitoの場合と同じ手順で、APIドキュメントのAmazon SESを開き、API v2 Reference から各アクションを選択します。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863714/d0e4b399-77bf-b26a-d554-132e708f2c2c.png)
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863714/6526da82-39bd-b454-5bb1-8f01616b80af.png)

SES V2サービスのSendEmailを例にとります。
```json:スタブ用JSONファイル
{
    "request": {
        "method": "POST",
        "url": "/v2/email/outbound-emails"
    },
    "response": {
        "status": 200,
	    "bodyFileName": "SendMailResponse.json",
	    "headers": {
	      "Content-Type": "application/json"
	    }
    }
}
```
```json:レスポンスを記述したファイル(SendMailResponse.json)
{
   "MessageId": "samplemessageId"
}
```

上記で、注意したい点は、Cognitoの時と違い、エンドポイントを、
``http://<WireMock起動のホスト>:<起動ポート番号>``
を前提としている点です。

そして、APIリファレンスのリクエスト先のURLを参考にして、URLを設定しています。
https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3863714/28b99e7b-b6b4-1631-5e29-1e4e6cd1af7b.png)

上記に基づき、スタブ用JSONファイルには、``/v2/email/outbound-emails`` をリクエストURLとして設定しています。

Cognitoの時と違い、SES V2の場合はリクエストURLで処理の振り分けを行っているため、URLの設定のみを行えばOKです。

レスポンスに関しては、この例ではサンプルはないので、Response Syntax を参考に、ファイルを作成します。

# エンドポイントURLに関して

各AWSサービスをWireMockにてMockするには、各サービスのエンドポイントURLを書き換える必要があります。

以下のURLを参考に、環境変数、もしくは、config共有ファイルにて、エンドポイントURLを設定します。

https://docs.aws.amazon.com/ja_jp/sdkref/latest/guide/feature-ss-endpoints.html

なお、上記で使用するサービス固有のエンドポイント識別子は以下にあります。

https://docs.aws.amazon.com/ja_jp/sdkref/latest/guide/ss-endpoints-table.html

ただし、https://docs.aws.amazon.com/ja_jp/sdkref/latest/guide/feature-ss-endpoints.html のAWS SDKsとの互換性の項にある通り、使用しているSDKにより、サポート有無が違います。
当方の主戦場であるJavaは残念ながらどのバージョンにおいてもサポートしていません。

そこで、プログラム中で直接エンドポイントURLを指定する必要があります。

## プログラム中でのエンドポイントの指定（Javaの例）
下記URLの"Choose a specific endpoint"の項の例の通り、直接プログラム中にエンドポイントURLを記載する必要があります。

https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/region-selection.html#region-selection-query-service

以下は、CognitoユーザープールのエンドポイントにMockのURLを設定する例です。

```java
CognitoIdentityProviderClient identityProviderClient = CognitoIdentityProviderClient.builder()
        .region(Region.of("ap-northeast-1"))
        .endpointOverride(URI.create("http://localhost:8111/cognito-idp"))
        .build();
```

SES V2のエンドポイントにMockのURLを設定する場合は、以下のような感じになります。
```java
SesV2Client sesv2Client = SesV2Client.builder()
        .region(Region.of("ap-northeast-1"))
        .endpointOverride(URI.create("http://localhost:8111"))
        .build();
```

## 上記の本番環境への適用
Mockではなく本番環境へ適用する場合は、エンドポイントに実際のAWSサービスのURLを設定しなければなりません。
実際の各サービスごとのエンドポイントに関しては、以下のURLに記載があります。

https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-service-information.html

エンドポイントURLに関しては、設定ファイルなどで外出しして、ローカル環境ではMockのURL、本番環境では、上記の実際のサービスのURLに接続されるように設定する必要があります。

# 最後に
AWSのサービスをWireMockでモックする例としてCognitoとSES V2について示しました。
サービスにより処理の癖が違ったりします。Cognitoではヘッダの値によって処理が振り分けられていましたし、SES V2は処理ごとにリクエストURLが違います。

また、リファレンスへの記載内容や粒度もサービスや処理ごとに癖があります。例えば、CognitoのAPIリファレンスのように、ヘッダで各処理が振り分けられる旨が書かれていなかったり、というケースがあったりしますし、処理によってはリクエストとレスポンスの仕様のみが記載されており、サンプルが記載されたなかったり、というケースもあります。
このように、少し注意が必要な点はありますが、とりあえずローカル環境では固定値を返却すればいい、というパターンにおいては、WireMockでモックするのが手っ取り早く出来るのではないかと思います。
もちろん、様々なエラーパターンなどを追加したい場合も、WireMockで定義することも可能です。