本記事はFirebase公式ドキュメントを噛み砕いた内容になってます。
公式ドキュメントにはテストツールについて触れられておらず個人的に進めずらかったので、環境構築の部分とテストツールとしてJestを利用する方法を織り交ぜてまとめました。
エミュレータの嬉しいところ
セキュリティルールのテストはFirestoreのローカルエミュレータを使用すると開発効率がUPします。
Firestoreのセキュリティルールが正しく記述できているかどうかをクライアント側でいちいち確認しようとすると、エラーが発生したときにPermission Deniedとしか出力されません。これでは原因調査がなかなか大変なのですが、ローカルエミュレータを使用するともう少し詳細な原因を提示してくれるのでルールの記述が捗って嬉しいです☺️
前準備として
FirebaseCLIでプロジェクトの設定は終わっている状態です。
firebase login
や firebase init firestore
でfirestore.rulesが作成されているとします。
サンプル リポジトリ
説明不足なところはサンプルを参考にしてください🙏
https://github.com/trueSuperior/Firestore-rules-test
Setup
package.json
{
"devDependencies": {
"@firebase/testing": "0.9.4",
"@types/jest": "23.3.9",
"jest": "23.6.0",
"filesystem": "1.0.1",
"source-map-support": "0.5.12",
"ts-jest": "22.4.6",
"ts-node": "8.1.0",
"typescript": "3.4.5"
}
}
@firebase/testing
がfirestoreのエミュレータを操作するためのモジュールになります。そのほかTypeScript
、Jest
等を入れます。
依存モジュールたちをインストールします。
npm i
別途tsconfig.json
の作成とpackage.json
にJestの設定が必要になります。
Firestoreエミュレータ
ローカルエミュレータのインストール
firebase setup:emulators:firestore
セキュリティルール
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read;
allow create: if request.auth.uid == userId && request.resource.data.createdAt == request.time;
}
match /rooms/{roomId} {
allow read;
// If you create a room, you must set yourself as the owner.
allow create: if request.resource.data.owner == request.auth.uid;
// Only the room owner is allowed to modify it.
allow update: if resource.data.owner == request.auth.uid;
}
}
}
サンプルとして公式のTypeScriptクイックスタートリポジトリのルールを使用します。
-
users
,rooms
コレクションは誰でも読み取り可能 - ユーザー自身のuidと登録する
users
のドキュメントIDが同一の場合のみusers
のドキュメントが作成可能 - 登録する
rooms
ドキュメントのowner
フィールドの値がユーザー自身のuidと同一の場合のみドキュメントを作成可能 - 作成済み
rooms
ドキュメントのowner
フィールドの値が自身のuidと同一の場合のみデータの更新が可能
余談なのですが。上記のルールではroomsのownerフィールドは変更後の制限をしていないため、自身のデータであれば好きに書き換えることができます。場合によってはチートに利用されてしまうケースがあるかと思うので注意が必要です。
@firebase/testingモジュールについて
import * as firebase from '@firebase/testing'
モジュールで利用する機能は主に4つあります。
- Firebaseアプリの作成
- ルールファイルの読み込み
- 読み書きの成功失敗をアサート
- クリーンアップ
Firebaseアプリの作成
- 特定ユーザーとして認証されたアプリ
const app = firebase.initializeTestApp({
projectId: "my-test-project",
auth: { uid: "alice", email: "alice@example.com" }
})
auth
にnull
を設定すると認証していないユーザーで初期化されます。
- 管理者として認証されたアプリ
const app = firebase.initializeAdminApp({ projectId: "my-test-project" })
ルールファイルの読み込み
firebase.loadFirestoreRules({
projectId: "my-test-project",
rules: fs.readFileSync("/path/to/firestore.rules", "utf8")
})
読み書きの成功失敗をアサート
- 失敗
firebase.assertFails(app.firestore().collection("private").doc("super-secret-document").get())
- 成功
firebase.assertSucceeds(app.firestore().collection("public").doc("test-document").get())
クリーンアップ
- プロジェクトに関連付けられているデータをクリア
firebase.clearFirestoreData({
projectId: "my-test-project"
})
- アプリの全消去
Promise.all(firebase.apps().map(app => app.delete()))
テストを書く
Jest記法について
import * as firebase from '@firebase/testing'
import * as fs from 'fs'
const testName = 'firestore-local-emulator-test'
const rulesFilePath = 'firestore.rules'
describe(testName, () => {
// はじめに1度ルールを読み込ませる
beforeAll(async () => {
await firebase.loadFirestoreRules({
projectId: testName,
rules: fs.readFileSync(rulesFilePath, 'utf8')
})
})
// test毎にデータをクリアする
afterEach(async () => {
await firebase.clearFirestoreData({ projectId: testName })
})
// 全テスト終了後に作成したアプリを全消去
afterAll(async () => {
await Promise.all(firebase.apps().map(app => app.delete()))
})
// describe('users collection tests', () => { ...
}
テスト構文はJestの記法になります。読むとなんとなくわかってもらえるかと思いますが簡単に説明すると以下になります。
-
describe
でブロックの作成 -
before~
,after~
で事前準備や後始末などを行う -
test
でテストの実行
describe
でブロックを作成するとテスト結果をみたときに文脈がわかりやすいのがいい感じです。個人的には'create', 'update'などでブロックをつくるのがおすすめです。
describeやtestの後に.onlyと繋げるとそのブロック以外のテストをスキップしてくれるので便利です。
describe.only('users collection tests', () => { ...
Jestの記法は他にもあるので興味があれば調べてみるとよいかと思います。
1つめのテストを書く
// users
describe('users collection tests', () => {
describe('read', () => {
test('should let anyone read any profile', async () => {
const db = authedApp(null)
const user = db.collection('users').doc('alice')
await firebase.assertSucceeds(user.get())
})
})
})
function authedApp(auth: object): firebase.firestore.Firestore {
return firebase
.initializeTestApp({ projectId: testName, auth: auth })
.firestore()
}
ようやく1つめのテストを作成することができましたのでさっそく実行します。
エミュレータの起動
firebase serve --only firestore
テストの実行
npm test
おめでとうございます!🎉
まず1つめのテストを記述することができました。
サンプルリポジトリではその他のテストも記述しているのでよければ参考にしてみてください。
おわりに
npm test -- --watch
でファイルの変更を監視してテストを走らせることができる機能もご活用ください。