エミュレータを使ってCircle CI上でテストされている方はいらっしゃるのですが、GitHub Actionsでの情報は見かけなかったのでまとめてみます。
大まかな流れは以下の通りです。
- firestoreエミュレータ入りのDockerfileを書く
- firestoreエミュレータを立ち上げるdocker-compose.ymlを書く
- GitHub Actionsのワークフロー上でdocker-composeを実行してテスト
なお、エミュレータはJavaで実装されているので、Javaの実行環境が無ければあらかじめインストールしておいてください。
セットアップ
まずはfirebase init
でFirestoreをプロジェクトにセットアップします。コマンドを実行するとどれをセットアップするかを聞かれるので、Firestoreを選択します。その後、セキュリティルールやインデックスファイルの名前を指定できますが、とりあえずエンター連打でデフォルトのまま進みます。
$ firebase init
(略)
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices.
Firestore: Deploy rules and create indexes for Firestore
=== Project Setup
(略)
? Please select an option: Use an existing project(ここでは既存のプロジェクトを選択していますが、新しく作っても以降の操作は同じです。)
(略)
=== Firestore Setup
(略)
? What file should be used for Firestore Rules? firestore.rules
(略)
? What file should be used for Firestore indexes? firestore.indexes.json
i Writing configuration info to firebase.json...
i Writing project information to .firebaserc...
i Writing gitignore file to .gitignore...
✔ Firebase initialization complete!
これで、プロジェクトに以下のファイルが生成されているはずです。
- firebase.json
- firestore.index.json
- firestore.rules
次にエミュレータをセットアップします。今回は説明のために先ほどのFirestoreセットアップと手順を分けましたが、実際はfirebase initで同時に行っても大丈夫です。
firebase initでEmulators
を選択すると、どのエミュレータをセットアップするか聞かれるので、Firestoreを選択します。あとは先ほどと同じように連打でOKですが、Would you like to download the emulators now?
ではY
を選択してください。そうすると、エミュレータのjarが落ちてきます。
$ firebase init
(略)
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices.
Emulators: Set up local emulators for Firebase features
(略)
=== Emulators Setup
? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection)
Firestore
? Which port do you want to use for the firestore emulator? 8080
? Would you like to download the emulators now? Yes
i Writing configuration info to firebase.json...
i Writing project information to .firebaserc...
✔ Firebase initialization complete!
firebase.json
にエミュレータの項目が追加されているはずです。
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
+ },
+ "emulators": {
+ "firestore": {
+ "port": 8080
+ }
}
}
ちゃんと動くか試してみましょう。以下のコマンドでエミュレータが起動します。
$ firebase emulators:start --only firestore
i emulators: Starting emulators: firestore
i firestore: Serving ALL traffic (including WebChannel) on http://localhost:8080
⚠ firestore: Support for WebChannel on a separate port (8081) is DEPRECATED and will go away soon. Please use port above instead.
i firestore: Emulator logging to firestore-debug.log
✔ firestore: Emulator started at http://localhost:8080
i firestore: For testing set FIRESTORE_EMULATOR_HOST=localhost:8080
✔ All emulators started, it is now safe to connect.
portを8080
に設定したので、localhost:8080
をlistenしています。HTTPリクエストを投げると、動いているかどうかが確認できます。
$ curl http:localhost:8080
Ok
セキュリティルールとテストを書いてみる
エミュレータが準備できたので、試しにセキュリティルールとそのテストを書いてみましょう。何かしらのプロジェクト一覧のうち、自分がオーナーであるプロジェクトだけが読み書きできるルールは以下のようになります。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /projects/{projectID} {
allow read, write: if resource.data.ownerId == request.auth.uid;
}
}
}
このセキュリティルールに対するテストは以下のようになります。Cloud FirestoreのSecurity RulesをCircleCIで自動テストする - ninjinkun's diaryを参考にJestで書いてみました。
import * as firebase from '@firebase/testing';
describe('/projects', () => {
const uid = 'alice';
const db = firebase.initializeTestApp({
auth: { uid, email: 'alice@example.com' },
projectId: 'my-test-project'
}).firestore();
const projectCollection = db.collection('projects');
it('can not list all projects', async () => {
await firebase.assertFails(projectCollection.get());
});
it('can list own projects', async () => {
await firebase.assertSucceeds(
projectCollection.where('ownerId', '==', uid).get()
);
});
});
Dockerの設定
ここまでで、ローカルでエミュレータが動くのを確認しました。次にこのエミュレータをDockerコンテナ上で動くようにします。firestore local emulator を docker-composeで動かす - Qiitaを参考にしました。
FROM node:10-alpine
WORKDIR /usr/src/app
RUN apk add --no-cache openjdk8-jre
RUN npm i -g firebase-tools && firebase setup:emulators:firestore
先ほどはfirebase init
でエミュレータを設定しましたが、Dockerfileではsetup:emulators
コマンドを利用しています。多分どちらでも同じだと思います。
docker-compose.ymlも書きます。セキュリティルールを読み込みたいので、カレントディレクトリをマウントしておきましょう。
version: '2'
services:
firestore:
build: .
ports:
- '8080:8080'
volumes:
- .:/usr/src/app
working_dir: /usr/src/app
command: 'firebase emulators:start --only firestore'
もしホスト側のポートを8080以外にする場合は、テスト実行側でFIRESTORE_EMULATOR_HOST環境変数を設定してください。
注意点として、エミュレータはデフォルトではlocalhostからのリクエストからしか受け付けないため、コンテナ外からのアクセスがそのままだとできません。そこで、firebase.json
を以下のように書き換えます。
"emulators": {
"firestore": {
+ "host": "0.0.0.0",
"port": 8080
}
}
これでコンテナ外からもアクセスできるようになります。
docker-compose up
で、コンテナ上でエミュレータが起動します。
$ docker-compose up firestore
Starting firebase-todoapp_firestore_1 ... done
Attaching to firebase-todoapp_firestore_1
firestore_1 | i emulators: Starting emulators: firestore
firestore_1 | i firestore: Serving ALL traffic (including WebChannel) on http://0.0.0.0:8080
firestore_1 | ⚠ firestore: Support for WebChannel on a separate port (8081) is DEPRECATED and will go away soon. Please use port above instead.
firestore_1 | i firestore: Emulator logging to firestore-debug.log
firestore_1 | ✔ firestore: Emulator started at http://0.0.0.0:8080
firestore_1 | i firestore: For testing set FIRESTORE_EMULATOR_HOST=0.0.0.0:8080
firestore_1 | ✔ All emulators started, it is now safe to connect.
GitHub Actions Workflowの設定
最後にWorkflowを設定します。オフィシャルのNodejsワークフローを参考に、以下のように設定しました。
name: Firebase CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x]
steps:
- uses: actions/checkout@v1
- name: docker-compose up
run: docker-compose up -d
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: yarn install, build, and test
run: |
yarn install
yarn tslint
yarn test
yarn build
env:
CI: true
docker-compose up
でエミュレータが起動され、yarn test
で先ほど書いたセキュリティルールを含むテストが実行されます。テスト実行開始までにエミュレータが上がっている保証はありませんが、惑星よりも重いことで有名なyarn install
を実行している間にまず間違いなく上がるので良しとしましょう。気になる方は定期的にcurlを叩いてレスポンスが返ってくるまで待つシェルスクリプトを書いたりすると良いと思います。
これでGitHub Actions上でFirestoreエミュレータを立ち上げてテストができるようになりました。実行時間は(Dockerfileのビルドからやっているので)docker-compose upが1分、yarn installが3分ぐらいです。
キャッシュをちゃんとやればもっと早くなるはずなので、調べたらまた記事を書きたいと思います。