最近(?)は, Firebaseを使ったアプリを開発してたりします
開発する過程で「Firestoreとかのルールをテストしたいな」と思うのは自然なことだと思います(???)
今回は, テストコードを書くうちに遭遇したミスとその原因を紹介したいと思います
改めて見ると「単純なミスだし注意不足だなぁ」って思いますが, まぁ記録を残す価値はあるんじゃないかなと思いつつ
@firebase/rules-unit-testing パッケージ
Firebase Firestore / Realtime Database / Storageのルールを検証するためのパッケージとして, Google様は「@firebase/rules-unit-testing」とかいう便利なパッケージを公開してくれています
firebase-toolsを使って建てたemulatorを使って, ルールの検証を行えるサンドボックス的環境を構築したうえで, 簡単にAuthの認証をmockしてテストできるという超便利なパッケージです
ルールのテストを効率的に行うにはこのパッケージを使うと楽なので, 私もこのパッケージを使ってテストコードをガリガリ書いてました
テストコードを書く
まずはサンドボックス的環境の構築を行うコードを書きます
import { initializeTestEnvironment, RulesTestEnvironment } from "@firebase/rules-unit-testing";
import fs from "fs";
const testEnv: RulesTestEnvironment = initializeTestEnvironment({
projectId: "demo-proj",
firestore: {
host: "localhost",
port: 8080,
rules: fs.readFileSync("firestore.rules", "utf-8")
}
});
とりあえずこれで初期化は完了します
で. projectId
をエミュレータ内でユニークなものにすることで, サンドボックス的な環境を(たぶん)何個でも作成することができます
もちろんこれを活用しない手はないだろう! ってことで, 汎用化すべく関数化します
import { initializeTestEnvironment, RulesTestEnvironment } from "@firebase/rules-unit-testing";
import fs from "fs";
export function getTestEnv(projectId?: string, rulesText?: string, rulesFilePath?: string, encoding?: BufferEncoding): Promise<RulesTestEnvironment> {
return initializeTestEnvironment({
projectId: "demo-" + (
projectId ?? Math.random().toString()
),
firestore: {
host: "localhost",
port: 8080,
rules: rulesText ?? fs.readFileSync(
rulesFilePath ?? "firestore.rules",
encoding ?? "utf-8"
)
}
});
};
めんどくさがりなので, テストごとに変更する可能性があるところは引数として指定できるようにして, 指定しなかった場合は既定値を使用するようにしました
プロジェクトIDはどうしてもテストごとにユニークなものにさせたかったんですが, 毎度毎度プロジェクトIDを指定するのも面倒だなぁと思ってしまいまして, demo-
に続いてランダムな数値を追加して使用する実装にしました.
ここからはとにかくコードをガリガリ書いて書いて書いて…
「いざデバッグ!」というときに…
{"error":{"code":500,"status":"UNKNOWN"}}
{"error":{"code":500,"status":"UNKNOWN"}}
at node_modules/@firebase/rules-unit-testing/src/impl/rules.ts:64:11
at step (node_modules/@firebase/rules-unit-testing/dist/index.cjs.js:74:23)
at Object.next (node_modules/@firebase/rules-unit-testing/dist/index.cjs.js:55:53)
at fulfilled (node_modules/@firebase/rules-unit-testing/dist/index.cjs.js:45:58)
「status : UNKNOWN」ってのに殺意が湧きますが… とりあえずstack-traceが出てるので, これを参考に例外がthrowされた場所を確認してみます
トランスパイル前のコードはローカルに無いので, GitHub上のコードを確認してみました. その結果, なんかfetchに失敗してエラーを吐いてるっぽいことがわかりました
fetchに失敗… 500とはいえエミュレータがバグってるとは思えないし, とりあえずルールはまだ編集前のやつだし, URLが間違ってる…?
と考えまして
makeUrl(hostAndPort, `/emulator/v1/projects/${projectId}:securityRules`),
とりあえずhost
とport
は大丈夫だろうから, あと関係ありそうなのはprojectId
だよなぁ… ってことでprojectId
を確認カクニン…
...
console.log
{
projectId: 'demo-0.9380899865895953',
firestore: {
host: 'localhost',
port: 8080,
rules: "rules_version = '2';\n" +
...
不要なの出力はさすがにコピペしてませんが… URLに拡張子じゃない「.
」ってなんか怪しいなぁと, 乱数を整数で出力するようにコードを修正してみました
import { initializeTestEnvironment, RulesTestEnvironment } from "@firebase/rules-unit-testing";
import fs from "fs";
export function getTestEnv(projectId?: string, rulesText?: string, rulesFilePath?: string, encoding?: BufferEncoding): Promise<RulesTestEnvironment> {
return initializeTestEnvironment({
projectId: "demo-" + (
projectId ?? Math.floor(Math.random() * Math.pow(10, 8)).toString()
),
firestore: {
host: "localhost",
port: 8080,
rules: rulesText ?? fs.readFileSync(
rulesFilePath ?? "firestore.rules",
encoding ?? "utf-8"
)
}
});
};
0~1の小数値から, 最大8桁の整数に変更してみました
無事にPASS
修正後にテストを実行した結果…
PASS __tests__/hoge.test.ts
✓ hoge Test (693 ms)
無事にテスト成功!
結論
Firebase EmulatorでprojectIdを指定する際にはピリオド(.
)を使用できない
言い訳(
公式ドキュメントには, 文字制約について書かれてなさげなんですよね
プロジェクトの作成フォームでは
使用できるのは英数字、スペース、および次の記号のみです。-!'"
って表示してくれてることに後で気づきました
スペースを使えるのに何でアンダーバーを使えないのかがわかりませんが, とりあえずエミュレータでもこの制約に従ってプロジェクトIDを指定する必要がありそうです
ちゃんと検証したわけじゃないので知りませんけど