💨

MSW と AWS SDK for JavaScript v3 を併用する場合の注意点

2022/05/21に公開

業務で結構ハマったのでメモ。

前提

発生した事象

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))

補足として、 S3Clientsend(GetObjectCommand) が返す GetObjectCommandOutput.Body: Readble | ReadbleStream<any> | Blob | undfinedReadble としてキャストし、 on('data', function) などを利用して stream の内容を取得する方法が公式の書き方だが、
以下の理由によりこちらのIssueを参考に get-stream を導入して利用していた。

  • 冗長すぎる
  • 簡単に文字列やオブジェクトにする方法が公式に無い
    • その他有志が公開してくれている情報を漁ってみたが、以下の記事のような fs.createWriterStream(fileName) を利用するようなパターンしか見当たらなかった
  • any が混入して TypeScript として適切ではない

調査

  1. 先ずは S3Client.send(GetObjectCommand) が返す GetObjectCommandOutput.Body の内容を確認した。
    • Readable である
    • statusCode などのプロパティは OK である
  2. stream.on('data', function) で実装し直してみて、それぞれのイベントが実行されているか cosole.log() で検証。
    • ログが出力されずにフリーズした
  3. ここらへんで、 MSW の Warning がログ出力されていることが気になり始めた。
    • ローカル環境では、MSW を利用して API リクエストを Mock しているが、この時点で MSW のハンドラ(Mock)は実装していない
    • 実際の処理は BFF 層で実行されるが、server.lithen() はデフォルトのまま
  4. 試しに MSW を無効化して再起動後に検証。
    • S3 のファイル内容を取得できた

まとめ

  • 真因まではわからなかったが(MSW のバグか、AWS SDK のバグか?)、ローカルサーバー上で MSWAWS SDKS3Client With minio は共存できないことがわかった。
  • ローカル上で実際の S3 アクセスを検証する場合は、MSW を無効化することにより確認できるが、
    MSW をメインとして Mock するのであれば、基本的にハンドラを書いておくのが妥当という結論に至った。

2022/11/12 追記

以下の対応をすることにより、MSW と AWS SDK on (minio or AWS S3) の共存が可能となった。

  1. MSW を 0.3.x からこの時点最新の 0.48.1 にアップデート
  2. 以下の Issue コメントの通り、next.config.js へ設定を追加する
    1. https://github.com/mswjs/msw/issues/1435#issuecomment-1279710481

AWS SDK v3 Client mock による S3 クライアントのモック化

2022/6/18 追記

aws-sdk-client-mockを利用することにより、
S3 クライアントを簡単にモックできる。
よって、API 系は MSW、S3 など AWS SDK を利用するところは aws-sdk-client-mock を使い分けることで共存可能となる。
※ Storybook 上でモックが効かない場合があるため注意

例)GetObjectCommandのモック
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 を生成する際に指定する S3ConfigforcePathStyle: true, の指定が必要となる。
※ 因みに公式サイトでは s3ForcePathStyle を指定するような記載があるが、これは v2 想定の指定方法であるため注意。

import { S3Client } from '@aws-sdk/client-s3'

const client = new S3Client({
    region: 'ap-northeast-1',
    forcePathStyle: true, // ローカル(minio)ではこいつを true にする
})

このエラーについても少しハマったが、その後に当記事の事象に遭遇してさらにハマったという経緯があったりする。

Discussion