GitHub ActionsでFirestore Emulatorを使った自動テストを行うためにやったこと
いろいろ苦戦したけど何とかできた。
やりたかったこととしては、Firestore EmulatorをGitHub Actions実行時のCIサーバで起動してCloud Functionsとfirestore.rulesのテストを行うこと。 まだrulesのテストはできてないが、雛形はできたのであとは流れでできそう。
ローカルでテストを導入する
実際の差分はこんな感じ。 github.com
ポイントになる部分について追記。
firebase emulators:exec を使う
package.jsonにtestのscriptを追加するが、こんな感じにした。
"scripts": { "test": "firebase emulators:exec 'jest --silent'" },
firebase emulators:exec
コマンドは便利で、その後のコマンド実行前にFirebase Emulatorのダウンロード、起動、コマンド実行終了時のFirestore Emulatorの停止をやってくれる。
jest --silent
にしたのは、テスト実行時にCloud Functionsのログが吐かれてテスト結果が見にくくなるため。
Cloud Functionsのindex.tsを分割
firebase-functions-test
でラップするためにCloud Functionsの実装がexportされたファイルをimportする必要があるが、最初全部のCloud Functionが入ったindex.tsをimportしようとして、nextもロードいろいろ設定が足りなくてエラーになってしまう。そこでnextとそれ以外のCloud Functionを分けて、next以外のCloud Functionだけimportするようにした。
src/functions ├── index.ts ├── tsconfig.json ├── __tests__ ├── firestore ← next以外(Firestore Trigger)のCloud Function └── web ←nextのCloud Function
algoliaのモック
ハマったけどこんな感じ。
let mockSaveObject: ReturnType<typeof jest.fn> jest.mock('algoliasearch', () => { mockSaveObject = jest.fn().mockReturnValue({}) return { default: () => ({ initIndex: () => ({ saveObject: mockSaveObject, }), }), } })
GitHub Actionsの導入
実際の差分はこんな感じ。 github.com
敢えて公開レポジトリでやっているが故にいろいろハマった。APIキーなどが入っている設定ファイルはすべてローカル(開発者のマシン上)にしかない状態にし、GitHub ActionsではそのファイルがなくてもFirestore Emulatorを起動させてテストが実行できるようにする必要があった。
結論としては設定ファイルの値がロードできなかった場合は適当な文字列をデフォルトでつけるようにした。
workflow設定
環境は現時点でのCloud Functionsの実行環境に合わせた。 初めてGitHub Actionsを使ったが、Dockerfileを書かなくてもこれでいい感じやってくれるのが嬉しい。
エミュレータを起動するプロジェクトを指定する必要があり、かつ指定するためにFirebaseの権限が必要になるため GCLOUD_PROJECT
、FIREBASE_TOKEN
を「GitHub > Settings > Secrets」から指定した。
name: CI on: [push] jobs: test: runs-on: ubuntu-18.04 env: FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} GCLOUD_PROJECT: ${{ secrets.GCLOUD_PROJECT }} steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: '10.18.1' - uses: actions/setup-java@v1 with: java-version: '15.0.2' java-package: jdk architecture: x64 - name: Install dependencies run: yarn - name: Build run: yarn preserve - name: Test run: yarn test --project ${GCLOUD_PROJECT}
この設定にする前に調べてみたが、GitHub ActionsでFirestore Emulatorを使うのにDocker Composeを使う例が多い。 - docker-compose × Firebase Emulatorでローカル環境構築 - GitHub Actions上でFirestoreエミュレータを立ち上げてテストする - Qiita
やれることが違いそうだけど今回はシンプルにCloud Functionとfirestore.rulesのテストをしたいだけなので、firebase emulators:exec
を使って特に複数コンテナは起動せずに実行することにした。設定がより複雑になりそうだったし。
functions.config()のところ
.runtimeconfigがあれば値を渡せるがpushしたくないので、こんな感じでデフォルト値を指定して、エラーにならないようにしておく。 この値を受け取る関数などは、当然本来あるはずの値が渡ってこないので、テスト時はモックする。
functions.config().algolia?.app_id ?? ''
フロントエンドで使うパラメータのところ
いままで config.ts
に書くようにしてgitignoreでpushしないようにしていたが、それだとCI側でビルドが通らなくなってしまう。
結論としてはnextで .env
が使えるのでそれを読み込むようにし、 .env
がなくて設定値が取れないときはデフォルトの値を読むようにした。
.env場所はsrc/appの下。クライアントサイドでも読み込む可能性のある値は NEXT_PUBLIC_
をprefixに付ける必要がある。
NEXT_PUBLIC_FIREBASE_API_KEY=xxx
最初JSONファイルを作って読み込ませようと思ったが、Dynamic Importもrequireもビルド時にそのファイルが無いとエラーになってしまうことを知った。
また firebase.initializeApp
に渡す値は空文字だとエラーになってしまう。
結果
まとまった時間がとれず随分時間がかかった。まだrulesのテストが無いので追加したい。
ビルドいらなかった
ここまで書いてふと「別にテストであれば実際にFirestore TriggerのCloud Functionが発火しなくてもいいわけで、firestoreのエミュレータだけ起動すれば、Cloud Functionsのビルドってしなくていいはずだよな...」と気づいてしまった。
やっぱりこれでよかった。無駄な苦労したかも。
まぁフロントエンドのテストもこれから書いてみたいし、.envは環境ごとに切り替えたりもできそうで便利だから、まぁいいか...。