firestoreのruleのテストは重要なのでメモ。
準備
作業場所の準備
作業ディレクトリを作ってfirebase init firestoreを実行。
mkdir rules-test
cd rules-test
firebase init firestore
私は既存プロジェクトを使うことが多いですが、テストなので新規でもなんでも。
❯ Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project
ルールやIndexを設定するファイル名を聞いてくるので、基本デフォでOK。
? What file should be used for Firestore Rules? firestore.rules
? What file should be used for Firestore indexes? firestore.indexes.json
テストに必要なモジュールのインストール
必要なモジュールをインストール。
テストツールとしてjest, firebaseのテストライブラリとしてfirebase/testing、設定ファイルを読み込むためfsを導入。
npm init -f
npm install -D jest @firebase/testing fs
package.jsonの編集
npm testでjestが実行されるようにしておきます。
.
"scripts": {
"test": "jest"
},
.
エミュレータのセットアップ
これがないとローカルでテストできません。まだsetup(ダウンロード)してないなら落とす。
firebase setup:emulators:firestore
エミュレータの起動
これはテスト実行直前でもいいです。
firebase serve --only firestore
テストの準備
jestは標準で*.test.js, *.spec.jsや__test__ディレクトリ下のファイルをテストとして認識するので、今回は__test__を作り、その下にfirebase.test.jsを置いてみます。
mkdir __test__
touch __test__/firestore.test.js
テストの実装
ルール
とりあえず認証してないとread, writeできないようにしています。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write:if request.auth != null;
}
}
}
テスト
認証ユーザー情報を指定し、read, writeを実行。
const firebase = require('@firebase/testing');
const fs = require('fs');
const project_id = "my-project-id";
describe("Firestoreのテスト", () => {
//実行前に一度だけ実行(初期化)
beforeAll(
async () => {
await firebase.loadFirestoreRules({
projectId: project_id,
rules: fs.readFileSync('./firestore.rules', 'utf8'),
});
}
);
//ブロックが終わるたび実行
afterEach(
async () => {
await firebase.clearFirestoreData({ projectId: project_id }); //データリセット
}
);
//終わった後に一度だけ実行
afterAll(
async () => {
await Promise.all(
firebase.apps().map((app) => app.delete()) //生成したアプリを削除
);
}
);
//条件(projectIdとauth情報)をの指定を関数化
//auth : {uid:'alice'}
//auth : {uid:'alice', admin:true} admin
//auth : null 未認証
function authedApp(auth) {
return firebase.initializeTestApp({
projectId: project_id,
auth: auth,
}).firestore();
}
describe("messagesコレクションのルールテスト", () => {
//読取りテスト
test("messageの読取り", async () => {
//条件(uidやprojectId)を指定してdbを生成
const db = authedApp({ uid: 'alice' });
//docRefを取得
const message = db.collection("message").doc("alice");
//取得ができるか
await firebase.assertSucceeds(message.get());
})
//書き込みテスト
test("messageの書き込み", async () => {
//条件(uidやprojectId)を指定してdbを生成
const db = authedApp({ uid: "alice" });
//docRefを取得
const message = db.collection("message").doc("alice");
//書き込みができるか
await firebase.assertSucceeds(
message.set({ text: "hoge" })
);
})
})
})
テスト実行
firestoreエミュレータを起動
別のコンソールを開いて、エミュレータを起動させておきます。
firebase serve --only firestore
テストの実行
テストを実行します。
npm test
下記のような感じでpassすればOKです。
> jest
PASS __test__/firestore.test.js
Firestoreのテスト
messagesコレクションのルールテスト
✓ messageの読取り (296ms)
✓ messageの書き込み (94ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.706s, estimated 8s
参考
少しだけ応用
テスト内容を一部変更
読取りテストの認証情報をnull(認証されてない状態)にしてテストを実行してみます。
テスト内容を変更
//読取りテスト
test("messageの読取り", async () => {
//条件(uidやprojectId)を指定してdbを生成
+ const db = authedApp(null);
//docRefを取得
const message = db.collection("message").doc("alice");
//取得ができるか
await firebase.assertSucceeds(message.get());
})
そうするとテストは失敗します。
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 1.616s, estimated 2s
Ran all test suites.
npm ERR! Test failed. See above for more details.
ルールを変更
readは認証なしでもOKのルールに変更してテストを実行してみます。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write:if request.auth != null;
}
}
}
正常にテストが通るはずです。
debug()関数の利用
認証情報をdebug()で囲んでやることにより、内容を出力することができます。
出力は、firestore-debug.logに出力されるようです。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
+ allow write:if debug(request.auth) != null;
}
}
}
auth情報まるまる出力してるのでややこしそうですが、認証情報としてaliceが使われているのがわかります。
map_value {
fields {
key: "uid"
value {
string_value: "alice"
}
}
fields {
key: "token"
value {
map_value {
fields {
+ key: "uid"
+ value {
+ string_value: "alice"
+ }
}
fields {
key: "iat"
value {
int_value: 0
}
}
fields {
key: "sub"
value {
string_value: "alice"
}
}
}
}
}
}
テストのデプロイ
編集したルール(だけ)をデプロイしたい場合は、下記のようにします。
firebase deploy --only firestore:rules