Firestore ローカルエミュレーターを試してみた。

Firebase Summit 2018Keynoteで発表されたFirestoreのローカルエミュレーターを試してみました。

Firestoreのセキュリティルールをテストする方法としてコンソールから使えるシミュレーターが以前からありましたが、今回発表されたのはローカルで実行できるエミュレーターです。
これを使えば、CI上でセキュリティルールのテストをルールをデプロイせずにできます。

試した環境は firebase-tools 6.0.1です。
最初は6.0.0で試してみたのですがどうやら日本語環境ではエミュレータがエラーになるようで6.0.1で一旦デフォルトで英語になるように修正されました。

ローカルエミュレーター

ローカルエミュレーターはFirebase Summit 2018で発表された手元の環境でFirebaseのデータベースであるRealtimeとFirestoreのセキュリティルールをテストすることができます。
今までセキュリティルールをテストしようと思うとコンソール上のシミュレーターで手動でテストをするか、実際のRealtimeDBやFirestoreにアクセスしにいかなければならなかったのですがローカルエミュレーターがあればテストも書きやすいし、CI上で並列にテストを実行することが可能になります。

ドキュメントはそれぞれ次の通りです。

Firestoreのローカルエミュレーターを試してみる

今回はFirestoreのローカルエミュレーターを試してみました。

まずはfirebase-toolsが最新であるか確認しましょう。私が試した環境は6.0.1です。

firebase --version

古い場合は最新バージョンにアップデートしておきましょう。

次にエミュレーターの機能を有効にしエミュレーター本体をダウンロードします。

firebase --open-sesame emulators
firebase setup:emulators:firestore

このコマンドが無事完了すればエミュレーターを利用することができます。

次のコマンドでエミュレーターを実行できます。

firebase serve --only firestore

起動ができれば API endpoint: http://localhost:8080とメッセージが表示されAPI Serverが立ち上がります。

次に実際にテストをしてみます。
今回は公式のサンプルを使います。

次のコマンドでサンプルを取得し、サンプルのカレントディレクトリに移動 npm installで必要なパッケージをダウンロードします。。

git clone https://github.com/firebase/quickstart-nodejs.git
cd quickstart-nodejs/firestore-emulator/javascript-quickstart
npm install

ディレクトリ構成は試したときは次のようになっていました。

.
├── README.md
├── firebase.json
├── firestore.rules
├── package-lock.json
├── package.json
└── test
└── test.js

package.jsonの中を見てみると script.testには mocha があるのがわかります。 test/test.jsmochaで書かれています。

テストを実行するには npm test コマンドを実行します。

My app
✓ requires users to log in before creating a profile (101ms)
1) should let anyone create their own profile
✓ should let anyone read any profile (82ms)
✓ should let anyone create a room (70ms)
✓ should force people to name themselves as room owner when creating a room (101ms)
✓ should not let one user steal a room from another user (161ms)
5 passing (743ms)
1 failing
1) My app
should let anyone create their own profile:
FirebaseError: 7 PERMISSION_DENIED: No matching allow statements

テストが終わるとこのようなメッセージでテストが失敗します。
 should let anyone create their own profile というテストが失敗したようです。
またFirebaseErrorでマッチするallow文がなかったことがわかります。

firestore.rules を見に行くと5行目にコメントアウトされている箇所があります。
このルールのコメントを外して保存してみてください。

保存して再度 npm test を実行すると次はテストが成功します。

My app
✓ requires users to log in before creating a profile (103ms)
✓ should let anyone create their own profile (78ms)
✓ should let anyone read any profile (82ms)
✓ should let anyone create a room (64ms)
✓ should force people to name themselves as room owner when creating a room (101ms)
✓ should not let one user steal a room from another user (136ms)
6 passing (696ms)

コードを見てみる

次にtest/test.js を見てみます。
中では @firebase/testing というパッケージがインポートされていることがわかります。

const firebase = require('@firebase/testing');

これはFirebase用のテストパッケージで2018-11–3 現在はNode.jsのみでサポートされているようです。

少し下に行くと次のコードがあります。

const rules = fs.readFileSync('firestore.rules', 'utf8');
function authedApp(auth) {
return firebase.initializeTestApp({
projectId: getProjectId(),
auth: auth,
}).firestore();
}
before(async () => {
// Create new project ID for each test.
testNumber++;
await firebase.loadFirestoreRules({
projectId: getProjectId(),
rules: rules,
});
});

このコードでセキュリティルールの読み込みとプロジェクトの初期化を行っています。

initializeTestApp を実行し引数に projectId を渡してprojectIdでなにか処理を変える場合のテストやauth{uid: 'alice'} のように渡すことでユーザーごとのロジックのテストも可能です。オブジェクトを渡すとフェイクのJWTを生成してくれるようなのでカスタム認証のカスタムクレームスも検証することが可能です。

次のように auth の引数に {uid: 'alice', admin: true} とカスタムクレームのパラメーターを渡します。

it('should let anyone create their own profile', async () => {
const db = authedApp({uid: 'alice', admin: true});
const profile = db.collection('users').doc('alice');
await firebase.assertSucceeds(profile.set({birthday: 'January 1'}));
});

そして先程コメントを外したセキュリティルールを次のように修正してみましょう。

allow write: if request.auth.token.admin == true;

npm test を実行して確認すると無事テストは通ります。
実際のコードでテストする場合は書き込みに失敗するテストも書くことを忘れないようにしておきましょう。

また loadFirestoreRules を実行することで動的にルールを読み込むことが可能になっています。

まとめ

まだエクスペリメンタル(実験段階)な機能ではありますがテストが書きやすくなりました。Summitから帰る飛行機の前に試してみたら日本語環境ではおかしくなることがわかり報告したら対応してくれました。

SummitについてはFirebase Japan User Groupで報告会が予定されています。参加できない方も配信を予定しているのでぜひ視聴してみてください。