firestore の rule のテスト方法について書きたいと思います.
firestore に限らず firebase のサービスの rule はセキュリティを確保するために非常に重要ですが、独特の書き方と概念なのでちゃんと書くのは結構難しいです。 また、条件が増えてくると手作業でテストするのも現実ではありません。
そこで今回は @firebase/rules-unit-testing
を使ったテスト方法について紹介したいと思います。内容的には以下の公式ドキュメントほぼそのままですが細かい導入方法などが欠けているのでそのあたりを補足したいと思います。
環境セットアップ
多分 firebase をいじっている人はすでに Firebase CLI (npm install -g firebase-tools
で入るもの)が入っていると思うのでインストールの説明は省きます。
プロジェクトの作業ディレクトリで firebase init firestore
して rule ファイルなどを作成します。エラーが出る場合は region の設定などが終わっていない or firestore をまだ有効化していない可能性があるのでエラーメッセージでググったりしてみてください。
firestore のリージョンは一回しか設定できないのでどこにすべきかよく検討してみてください(日本で展開するサービスなら asia-northeast1 (東京) が無難かと思います)。
テスト環境セットアップ
nodejs でテストを書きます。具体的な書き方がわからない場合はこのリポジトリが参考になります。
quickstart-testing/unit-test-security-rules at master · firebase/quickstart-testing · GitHub
以下の2つのコマンドを順番に叩いていけばセットアップ完了です
npm init -y
- すでに npm init している場合は不要です. iOS や Android アプリを作っていてまだ行っていない場合はしてください.
npm install -D mocha @firebase/rules-unit-testing
# テストに使うライブラリ- test runner に mocha を使っていますが好みがあれば別のものでいいと思います
package.json の scripts を以下のように編集しておきます。
{ "scripts": { "test-firestore": "mocha --exit firestore.spec.js", "test": "mocha --exit *.spec.js" } }
テストを書く
細かい書き方は先に紹介した公式ドキュメントやリポジトリを参考にしてください。ここではこれらのドキュメントの理解の助けになるように大まかな流れを説明します。
- rule の読み込み (
firebase.loadFirestoreRules
の呼び出し)- 1回だけやればいいので
before
でやっています
- 1回だけやればいいので
- firestore データの初期化(
clearFirestoreData
)- 各テストケースできれいな状態を作りたいので
beforeEach
でやっています
- 各テストケースできれいな状態を作りたいので
- ユニットテストを行います
- 最後のカバレッジレポートを出力します(after でやっています)
細かい補足
- 初期データのセットアップなど管理者権限で行いたい操作は
firebase.initializeAdminApp
で初期化したインスタンスを使うと便利です - テスト中に認証ユーザーを変えて色々テストしたいので
getAuthedFirestore
のようなメソッドで毎回initializeTestApp
すると便利ですinitializeTestApp
で取得したインスタンスは特定のユーザーで認証している状態(or 全く認証していない状態)を作り出せます
- テストは基本的に
firebase.assertFails
とfirebase.assertSucceeds
を使って書きます.- 書き込みや読み込みしたときに失敗することを期待する場合は
assertFails
、 成功を期待する場合はassertSucceeds
を使います
- 書き込みや読み込みしたときに失敗することを期待する場合は
具体的なコードは以下のような感じになります。より詳細は quickstart-testing/unit-test-security-rules at master · firebase/quickstart-testing · GitHub を参考にしてください。
// 色々省略しています const admin = firebase.initializeAdminApp({projectId: PROJECT_ID}) function getAuthedFirestore(auth) { return firebase .initializeTestApp({ projectId: PROJECT_ID, auth }) .firestore(); } beforeEach(async () => { await firebase.clearFirestoreData({ projectId: PROJECT_ID }) }) before(async () => { await firebase.loadFirestoreRules({ projectId: PROJECT_ID, rules: fs.readFileSync("../firestore.rules", "utf-8") }) }) after(async () => { await Promise.all(firebase.apps().map(app => app.delete())) const coverageFile = "firestore-coverage.html" const fstream = fs.createWriteStream(coverageFile) await new Promise((resolve, reject) => { http.get(COVERAGE_URL, (res) => { res.pipe(fstream, { end: true }); res.on("end", resolve); res.on("error", reject); }); }); console.log(`View firestore rule coverage information at ${coverageFile}\n`); }) describe("firestore/hogehoge_data", () => { it("認証していないユーザーは読めない", () => { const db = getAuthedFirestore(null) firebase.assertFails(db.collection("hogehoge_data").get()) }) it("認証ユーザーは読める", () => { const db = getAuthedFirestore({ uid: "user_name" }) firebase.assertSucceeds(db.collection("hogehoge_data").get()) }) it("認証ユーザーでも書き込み不可", () => { const db = getAuthedFirestore({ uid: "user_name" }) firebase.assertFails(db.collection("hogehoge_data").add({text: "hoge"})) }) }) describe("firestore/hugahuga_data", () => { it("認証していないと読み書き不可", () => { const db = getAuthedFirestore(null) firebase.assertFails(db.collection("hugahuga_data").doc("hoge").get()) firebase.assertFails(db.collection("hugahuga_data").doc("hoge").set({data: "data"})) }) })
テストの実行
テストコードで FIRESTORE_EMULATOR_HOST
という firebase が設定する環境変数を参照しているので以下のように firebase コマンド経由でテストを実行します.
firebase emulators:exec --only firestore "npm run test-firestore"
こうすると エミュレータの起動 -> テスト実行という感じに実行されます. ruby でよくやる bundle exec hogehoge
と同じです.
テストが無事実行されるとカバレッジリポートも生成されるので見ておきましょう. このレポートはリポジトリに含めずに gitignore しちゃってもいいと思います。
デプロイ
テストが無事通ったら firebase deploy --only firestore
で rule をデプロイしましょう.
まとめ
firestore の rule の書き方を説明しました。 公式のドキュメントだと説明が省かれている箇所があるので補足するような情報も載せておきました。
ぜひ rule のテストをして安心安全なアプリ開発を行ってください。
こぼれ話
ずっと前に書いた以下の記事が未だに見られていて嬉しいのですが、情報が古いので怖くもあります。