firebaseをどうテストするか
firebaseをいじってて少しだけ知見がたまってきたので共有します。
前提
ローカルでテストしたいなら、Firebase Local Emulator Suite を使いましょう。
yarn run firebase emulators:start
で起動できます。
yarn firebase emulators:exec 'yarn test'
ってやると、emulator起動してからテストしてくれます。
設定
これを書いておきましょう。ローカルでの起動でemulatorを使うようになります。create-react-appを使ってるのでNODE_ENV
で分岐してますが、別に他のでもいいです。あとポートは自分のに合わせましょう。
const app = firebase.initializeApp(firebaseConfig); export const db = app.firestore(); if (process.env.NODE_ENV !== "production") { db.settings({ host: "localhost:8080", ssl: false, }); app.functions().useFunctionsEmulator("http://localhost:5001"); }
firestore
rulesのテストは簡単です。@firebase/rules-unit-testing
を使えば下のようなコードですぐテストできます。(@firebase/testingから改名しました)
import { assertFails, initializeTestApp, } from "@firebase/rules-unit-testing"; test("denied if not authed", async () => { const notAuthedDB = initializeTestApp({ auth: { uid: undefined } }).firestore(); const userRef = notAuthedDB.doc("/users/uid"); await assertFails(userRef.set({ hoge: "hoge" })); });
もしrulesを回避して先にテスト用データを用意しておきたいなら、initializeAdminApp
を使いましょう*1。
firebase.rules
はちゃんと設定している限り自動で読み込まれるので、loadFirestoreRules
をbeforeEach
で読み込んだりする必要はないはずです。
問題なのが、以下のような関数をテストする場合です。
export const complexTransaction = async () => { // 複雑なDB操作... }
これに関してはスマートな解決策を見つけられていません。一応jestの場合は、
jest.mock("firebase/app", () => { // eslint-disable-next-line @typescript-eslint/no-var-requires const { initializeTestApp, firestore } = require("@firebase/testing"); const mockedApp = initializeTestApp({ projectId: "pid", auth: { uid: "uid" }, }); return { initializeApp: () => mockedApp, firestore: firestore, }; });
というようにmockすればなんとかテストできますが、あまり綺麗ではないですね。最悪adminでfirebase.firestore
をemulatorにつなげてくれればいいんですが……。
functions
import * as admin from "firebase-admin"; import * as funcTest from "firebase-functions-test"; const db = admin.firestore(); const tester = funcTest({ projectId: process.env.GCLOUD_PROJECT }); const makeDocumentSnapshot = tester.firestore.makeDocumentSnapshot; const makeChange = tester.makeChange; const wrapped = tester.wrap(yourAwesomeFunction); test("assert Firestore", async () => { // arrange const roomId = "sefasef"; const roomPath = `rooms/${roomId}`; const beforeData = { hoge: "hoge", }; const before = makeDocumentSnapshot(beforeData, roomPath); const afterData = { hoge: "fuga", }; const after = makeDocumentSnapshot(afterData, roomPath); const change = makeChange(before, after); // act await wrapped(change, { params: { roomId } }); // assert const roomRef = db.doc(roomPath); expect((await roomRef.get()).data()).toEqual(afterData); });
こんな感じでいけます。functionsのテストは比較的簡単です。あと、Warning, estimating Firebase Config based on GCLOUD_PROJECT. Initializing firebase-admin may fail
と警告がでます。これは正直わからん。。。
*1:initializeTestAppのprojectIdと同じもので初期化する必要があります