Firestoreのデータをcopy/exporrt/importできるfsrplを使ってunit testを書く


こんにちは。もぐめっとです。

皆さんは上記を見てなにか叫びたくなりませんか?
是非その叫んだ内容をコメント欄で書いて答え合わせしましょう。

みなさん、fsrplというコマンドを知ってますか?

firestoreのデータをイジイジできるコマンドなんですけど、これが最近気づいたらリリース版になっていて、reference型が対応してなかったのがとうとう対応して超神コマンドと化してしました。

今回はfsrplを使ってセキュリティールールのunit testを書いてみたのでご紹介です。

fsrplの使い方

READMEにわかりやすく書いてあるので詳細はそちらに譲るとして、大体の使い方とコマンドだけ転載いたします。

インストール

brew tap matsu0228/homebrew-fsrpl

brew install fsrpl

事前準備として最初に環境変数で、firebaseの秘密鍵のjsonのパスを指定しておきます

export FSRPL_CREDENTIALS=./path/to/adminsdk.json

コピーする時

fsrpl copy [コピー元のドキュメントを指定] --dest [コピー先のドキュメントを指定] 

exportする時

fsrpl dump [コピー元のドキュメントを指定] --path [バックアップファイルを保存するディレクトリを指定]

importする時

fsrpl restore [コピー元のドキュメントを指定] --path [復元対象のJSONファイルを指定]

security ruleのunit testを書く

npmで環境を整えます。こんな感じ。

{
  "scripts": {
    "test": "tsc && FIRESTORE_EMULATOR_HOST=localhost:8080 FIREBASE_DATABASE_EMULATOR_HOST=localhost:9000 npx jest",
    "watch": "npm test -- --watch",
    "emulator:start": "firebase emulators:start --only firestore",
  },
  "devDependencies": {
    "@firebase/app-types": "^0.6.1",
    "@firebase/firestore-types": "^1.13.0",
    "@firebase/rules-unit-testing": "^1.0.7",
    "@types/chai": "^4.2.13",
    "@types/jest": "^26.0.14",
    "filesystem": "1.0.1",
    "jest": "^26.5.2",
    "jest-environment-uint8array": "^1.0.0",
    "mocha": "^8.1.3",
    "source-map-support": "0.5.19",
    "ts-jest": "^26.4.1",
    "ts-node": "9.0.0",
    "tslint": "^6.1.3",
    "typescript": "^4.0.3"
  },
  "dependencies": {
    "firebase-admin": "^9.2.0",
    "jest-environment-node": "^26.0.1"
  }
}

FIRESTORE_EMULATOR_HOSTで環境変数をセットておくのが味噌です。
FIREBASE_DATABASE_EMULATOR_HOSTはセットしておかないと警告出てうるさかったので使わないけどセットしておきました。

そしてテストはこんな感じで実装できました

import TestProvider from "./TestProvider"
import { execSync } from "child_process";
import * as firebase from "@firebase/rules-unit-testing"
const provider = new TestProvider()
const projectId = "hoge"
const uid = "d5CsSU2fXhUtRx8JqKBbqREVq883"
const clientFirestore = firebase.initializeTestApp({ projectId, databaseName: "dbName", auth: {uid: uid} }).firestore()
const emulatorHost = process.env.FIRESTORE_EMULATOR_HOST
describe('test', () => {
  afterEach(async () => {
    await firebase.clearFirestoreData({projectId: projectId})
  })

  // users
  describe("users collection tests", () => {
    describe("read", () => {
      test("認証してれば読み取れる", async () => {
        const dataPath = "users/*"
        const mockPath = "./mock/users"
        const restoreCmd = `FIRESTORE_EMULATOR_HOST=${emulatorHost} fsrpl restore "${dataPath}" --path "${mockPath}" --debug --emulators-project-id=${projectId}`;
        const stdout = await execSync(`${restoreCmd}`);
        console.warn(`imported test data to ${dataPath}: ${stdout.toString()}`);
        await firebase.assertSucceeds(clientFirestore.doc(`users/${uid}`).get())
      })
    })
  })
})

execSyncを通してfsrplを実行することで簡単にテストデータが用意できます!素敵!

テストを実行するときはエミュレータを予め実行しておきます。

npm run emulator:start

別ターミナルでテストを実行。

npm run test

@mogmetの所感

今までは頑張ってコードで書いてデータを準備してましたが、fsrpl使えばjsonでモックデータを準備しておくだけで簡単にテストがかけそうです。

cloud functionsのunit testの時とかに特に重宝するんじゃないかと思います。

欠点をいうと、security ruleでは書き込みの時のテストの時にmockのデータを使いたい感じになると思うんですけど、fsrplだとセキュリティールール通さず直に書き込んでしまってテストにならないので、一回fsrplでロードした後、データをgetして、getしてきたデータを使って書き込みテストするとかになるのかなぁとか構想してますが、なんか二度手間感があるので他にいい方法ご存じの方いたら教えて下さい。

ただ、テスト以外にもコンソールでfirestoreのデータをポチポチ準備するのはつらいんで、積極的にこのコマンドを使ってデータを準備できればと思いました!