firestoreを使ったコードのユニットテストをする

2024/03/09 荒井 雄治朗

初期費用は安いしスケールもしやすいので僕はよくFirestoreを使っている。 プログラムの中のデータベースの部分はモックにしないでなるべくデータを実際にデータベースに入れてテストをしたいと思っているが、firestoreだとなかなかうまくいかなかったので今の実際にやっている方法をメモする。

前提

テストの設定は以下の環境で行なっている。 システムはNext.js + Typescriptで作っている。テストの実行にはvitestを使っていて、テストをはfirebase-adminでサーバーサイドのコードのみをテストしている。
データベースはFirebase ローカル エミュレータ スイートを使ってローカルで実行している。
サーバーサイドのみテストをしているのは、クライアントサイドのコードをテストしようとしてもうまくFirebase ローカル エミュレータ スイートに接続できなかったから諦めたから。

vitestの設定

vitest.config.ts

setup.ts を読み込むように設定する。

import { defineConfig } from "vitest/config"
 
export default defineConfig({
  test: {
    setupFiles: ["setup.ts"],
    coverage: {
      provider: "istanbul",
      reporter: ["text-summary", "html"],
    },
  },
})

setup.ts

テストの実行前にinitializeAppを実行するように設定する。

// dotenvファイル .env.testing を読み込む
require("dotenv").config({ path: ".env.testing" })
 
import { initializeApp } from "firebase-admin"
import { cert } from "firebase-admin/app"
 
// firebaseを初期化
initializeApp({
  credential: cert(require("./firebase-adminsdk.json")),
})

.env.testing

firebase ローカル エミュレータ スイートを使うための設定をする。

FIREBASE_PROJECT_ID=demo-firestore-testing
FIRESTORE_EMULATOR_HOST=127.0.0.1:8080
STORAGE_HOST=127.0.0.1:9199
FIREBASE_AUTH_EMULATOR_HOST=127.0.0.1:9099

firebase-adminsdk.json

firebase-adminsdk.json はFirebaseのプロジェクトの設定からサービスアカウントのキーをダウンロードして作成する。 project_idはFIREBASE_PROJECT_IDに設定したプロジェクトIDと合わせる。

{
  "type": "service_account",
  "project_id": "demo-firestore-testing",
  "private_key_id": "xxx",
  "private_key": "xxx",
  "client_email": "xxx",
  "client_id": "xxx",
  "auth_uri": "xxx",
  "token_uri": "xxx",
  "auth_provider_x509_cert_url": "xxx",
  "client_x509_cert_url": "xxx",
  "universe_domain": "googleapis.com"
}

テストの実行前にデータを削除する

lib/firebaseTest.ts

テスト実行毎にデータを削除する beforeEachCleanFirestore を定義する。 このURLにDELETEリクエストを送ることでfirestoreのデータを削除できる。 テスト間でデータベースをクリアする

import { beforeEach } from "vitest"
 
export function beforeEachCleanFirestore() {
  beforeEach(async () => {
    await fetch(`http://localhost:8080/emulator/v1/projects/${process.env.FIREBASE_PROJECT_ID}/databases/(default)/documents`, {
      method: "DELETE",
    })
  })
}

テストの実行

package.json

package.json にテストのスクリプトを設定して実行する。 並行実行すると他のテストと競合してエラーが発生するので、--no-file-parallelismを設定している。

  "scripts": {
    "test": "vitest --watch --no-file-parallelism",
  },

テスト

設定をした上で、以下のようにテストを書くとエミュレータにアクセスしてテストを実行できる。

import { getFirestore } from "firebase-admin/firestore"
import { expect, test } from "vitest"
import { beforeEachCleanFirestore } from "../firebaseTest"
 
beforeEachCleanFirestore()
 
test("firestoreのテスト", async () => {
  const dataSnap = await getFirestore().collection("Data").add({
    name: "test",
  })
 
  const result = await getFirestore().collection("Data").doc(dataSnap.id).get()
  expect(result.data()).toEqual({ name: "test" })
})