MSW と AWS SDK for JavaScript v3 を併用する場合の注意点
業務で結構ハマったのでメモ。
前提
- Next.js 12/TypeScript
- MSW
- AWS SDK for JavaScript v3
- MINIO(Docker)
発生した事象
yarn dev
でローカルサーバーを起動して動作確認をしていたところ、
@aws-sdk/client-s3を利用して minio
コンテナでホスティングしている S3 のファイル内容を取得する処理で、何故か取得した内容が空の文字列となった。
・・・
const cmd = new GetObjectCommand({
Bucket: 'test',
key: 'file.json',
})
const cmdOutput = client.send(cmd)
// ここの ret が空文字列!!Why??
const ret = getStream((cmdOutput.Body as Readable))
補足として、 S3Client
の send(GetObjectCommand)
が返す GetObjectCommandOutput.Body: Readble | ReadbleStream<any> | Blob | undfined
を Readble
としてキャストし、 on('data', function)
などを利用して stream の内容を取得する方法が公式の書き方だが、
以下の理由によりこちらのIssueを参考に get-stream を導入して利用していた。
- 冗長すぎる
- 簡単に文字列やオブジェクトにする方法が公式に無い
- その他有志が公開してくれている情報を漁ってみたが、以下の記事のような
fs.createWriterStream(fileName)
を利用するようなパターンしか見当たらなかった
- その他有志が公開してくれている情報を漁ってみたが、以下の記事のような
-
any
が混入して TypeScript として適切ではない
調査
- 先ずは
S3Client.send(GetObjectCommand)
が返すGetObjectCommandOutput.Body
の内容を確認した。-
Readable
である -
statusCode
などのプロパティは OK である
-
-
stream.on('data', function)
で実装し直してみて、それぞれのイベントが実行されているかcosole.log()
で検証。- ログが出力されずにフリーズした
- ここらへんで、
MSW
の Warning がログ出力されていることが気になり始めた。- ローカル環境では、
MSW
を利用して API リクエストを Mock しているが、この時点でMSW
のハンドラ(Mock)は実装していない - 実際の処理は BFF 層で実行されるが、server.lithen() はデフォルトのまま
- ローカル環境では、
- 試しに
MSW
を無効化して再起動後に検証。- S3 のファイル内容を取得できた
まとめ
- 真因まではわからなかったが(MSW のバグか、AWS SDK のバグか?)、ローカルサーバー上で
MSW
とAWS SDK
のS3Client With minio
は共存できないことがわかった。 - ローカル上で実際の S3 アクセスを検証する場合は、
MSW
を無効化することにより確認できるが、
MSW
をメインとして Mock するのであれば、基本的にハンドラを書いておくのが妥当という結論に至った。
2022/11/12 追記
以下の対応をすることにより、MSW と AWS SDK on (minio or AWS S3) の共存が可能となった。
- MSW を
0.3.x
からこの時点最新の0.48.1
にアップデート - 以下の Issue コメントの通り、
next.config.js
へ設定を追加する
AWS SDK v3 Client mock による S3 クライアントのモック化
2022/6/18 追記
aws-sdk-client-mockを利用することにより、
S3 クライアントを簡単にモックできる。
よって、API 系は MSW
、S3 など AWS SDK を利用するところは aws-sdk-client-mock
を使い分けることで共存可能となる。
※ Storybook 上でモックが効かない場合があるため注意
import { Readable } from 'stream'
import { mockClient } from 'aws-sdk-client-mock'
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
const s3Mock = mockClient(S3Client)
s3Mock.reset()
s3Mock.on(GetObjectCommand, {
Bucket: 'bucket-name',
Key: 'filename.json',
}).resolves({
Body: Readable.from(JSON.strigfy({ json: 'data' }))
})
Tips
当記事でメインとしている事象の前に、この記事と似た以下のエラーが発生していた。
Error: getaddrinfo ENOTFOUND <GetObjectCommand.Bucketに指定したバケット名>.localhost
このエラーについては、上記記事にあるような /private/etc/hosts
の設定が原因では無く、
minio
アクセスに AWS SDK を利用する場合は、
S3Client
を生成する際に指定する S3Config
の forcePathStyle: true,
の指定が必要となる。
※ 因みに公式サイトでは s3ForcePathStyle
を指定するような記載があるが、これは v2 想定の指定方法であるため注意。
import { S3Client } from '@aws-sdk/client-s3'
const client = new S3Client({
region: 'ap-northeast-1',
forcePathStyle: true, // ローカル(minio)ではこいつを true にする
})
このエラーについても少しハマったが、その後に当記事の事象に遭遇してさらにハマったという経緯があったりする。
Discussion