🔥

rules-unit-testingがv2になったので変更点とともに紹介する

2021/10/04に公開2

1ヶ月ほど前に、Firestoreのルールの単体テストに使う @firebase/rules-unit-testing がメジャーバージョンアップデートされました(v1 → v2)。個人的にこのバージョンアップによる改善は、わかりやすくかつ使いやすくなっていて好感を持てたので紹介します。

ちなみに、Firebase JS SDKを v8 → v9 にアップした際に気づきました。
https://twitter.com/_mogaming/status/1444540068358549510?s=21

変更点

公式ドキュメントでもしっかり説明されているのでこちらもご参照ください。
https://firebase.google.com/docs/rules/unit-tests

TypeScriptで紹介していきます。実際に動いているものが見たい方は以下のリポジトリを見るのが手っ取り早いかと思います。
https://github.com/mogaming217/firebase-rules-unit-testing-v2

@firebase/rules-unit-testing2.0.1 で動かしています。

v1(以前までのバージョン)

import { v4 as randomString } from 'uuid'
import * as firebase from '@firebase/rules-unit-testing'

type Auth = {
  uid?: string
  // other fields are used as request.auth.token in firestore.rules
  [key: string]: any
}

const projectId = randomString()
const databaseName = randomString()

// ルールの読み込み
const rules = fs.readFileSync(path.resolve(__dirname, './firestore.rules'), 'utf8')
await firebase.loadFirestoreRules({ projectId, rules })

// firebase-admin でのFirestoreインスタンスを得る
const adminDB = firebase.initializeAdminApp({ projectId, databaseName }).firestore()

// firebase client SDK でのFirestoreインスタンスを得る
const auth: Auth = { uid: randomString() }
const clientDB = firebase.initializeTestApp({ projectId, databaseName, auth }).firestore()

// テスト後のデータ削除
await firebase.clearFirestoreData({ projectId })
  • Firestoreのインスタンスを得るには、毎回 projectId, databaseName を渡していました。
    • ちょっと面倒だった
  • Admin, Client SDKでのFirestoreインスタンスが入り混じっていた
    • ルールをバイパスして事前データを作成するにはAdminが必要だった
    • あまりないと思うが serverTimestamp を Admin の Firestore インスタンスに渡す場合、 firebase-admin(Admin SDK) から得た serverTimestamp を渡さないとエラーになって面倒だった

v2(新しいバージョン)

import { v4 as randomString } from 'uuid'
import * as firebase from '@firebase/rules-unit-testing'

const projectId = randomString()

// テストプロジェクト環境の作成
const testEnv: firebase.RulesTestEnvironment = await firebase.initializeTestEnvironment({
  projectId,
  firestore: {
    rules: fs.readFileSync('./firestore.rules', 'utf8')
  }
})

// ログイン情報つきのContextを作成し、そこから Firestore インスタンスを得る
const authenticatedContext = testEnv.authenticatedContext('uid string')
const clientDB = authenticatedContext.firestore()

// ゲストContextを作成し、そこから Firestore インスタンスを得る
const unauthenticatedContext = testEnv.unauthenticatedContext()
const guestClientDB = unauthenticatedContext.firestore()

// ルールをバイパスしたContextを作成し、そこから Firestore インスタンスを得る
await testEnv.withSecurityRulesDisabled(async context => {
  // v1での adminDB のように振る舞えるDB。実態としては Client SDK からのルールが適用されない Firestore インスタンス。
  const noRuleDB = context.firestore()
  // 事前データの作成など
})

// Firestoreのデータ削除
await testEnv.clearFirestore()
  • ある一定のテスト単位ごとに RulesTestEnvironment を作成して、すべてはこの変数を通して操作する
  • RulesTestEnvironment 自体に projectId が設定されるのでいちいち指定する必要がなくなった
    • databaseName も不要になった(Firebase Firestoreでは指定できないしこのほうが自然)
  • すべての Firestore インスタンスは Client SDK からのものになりシンプルになった
    • serverTimestamp などを渡す場合は、 JS SDKの firebase/firestore もしくは import firebase from 'firebase/compact'firebase.firestore.FieldValue.serverTimestamp() で渡す
    • firebase/compactrules-unit-testing についてくる

おわりに

v2になり、かなりシンプルで理解しやすい構造になって、素晴らしい改善だなと感じました。いままでのテストの書き方によっては地味に書き換え箇所が多くなりそうです(実際ボクが担当しているプロジェクトでは結構書き換えが必要だった)。

Discussion

ウチイダユウゴウチイダユウゴ

rules-unit-testing v2の全体像つかみ切れてなかったので、日本語コメント付きの記述例が大変参考になりました。
ありがとうございます!