はじめに
FirebaseをローカルでテストできるFirebase Local Emulator SuiteをDocker環境で構築、アプリケーション(Go)から使用する機会がありました。
本記事では、Dockerでの構築方法及び、アプリケーション側の設定について記載していきます。
※今回は、Firestoreでの利用となります。
環境
- macOS Monterey 12.4
- docker desktop 4.7.0
- Go 1.18.3
構築
プロジェクト構成は以下の前提で進めていきます。
firebase設定ファイルすでにある事を想定し、実際の環境とも併用することを考慮して、使いまわします。
root/
├ docker/
│ ├ firebase/
│ │ ├ bin/
│ │ ├ config/
│ └ └ Dockerfile
├ src/
├ .firebaserc
├ firebase.json
├ firestore.indexes.json
├ firestore.rules
└ docker-compose.yml
エミュレータを使用するために、firebase.json
に以下の設定を追加(ポート番号は状況に応じて変更)
"emulators": {
"auth": {
"host": "0.0.0.0",
"port": 9099
},
"functions": {
"host": "0.0.0.0",
"port": 5001
},
"firestore": {
"host": "0.0.0.0",
"port": 8080
},
"database": {
"host": "0.0.0.0",
"port": 9000
},
"hosting": {
"host": "0.0.0.0",
"port": 5050
},
"pubsub": {
"host": "0.0.0.0",
"port": 8085
},
"storage": {
"host": "0.0.0.0",
"port": 9199
},
"ui": {
"enabled": true,
"host": "0.0.0.0",
"port": 4000
}
}
Dockerの設定
Emulatorを使用するために、
- Node.js バージョン 8.0 以降。
- Java JDK バージョン 11 以降。
- Firebase CLI
が必要なため、各種インストール。
FROM ubuntu:22.04
RUN apt update && \
apt install -y curl openjdk-17-jdk && \
curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \
apt install -y nodejs
RUN npm install -g firebase-tools
firebaseを使用するためのアプリケーション及び上記のDockerfileを指定したcomposeファイルを作成します。
ポートは同様のものを指定し、docker立ち上げ時に自動でエミュレータが起動するようにfirebase emulators:start
を指定しておきます。
version: '3.9'
services:
app:
build:
context: .
dockerfile: ./docker/app/Dockerfile
env_file: .env
ports:
- "3000:8080"
volumes:
- ./src/:/go/src
tty: true
firebase:
build:
context: .
dockerfile: ./docker/firebase/Dockerfile
volumes:
- ./.firebaserc:/opt/firebase/.firebaserc
- ./firebase.json:/opt/firebase/firebase.json
- ./firestore.indexes.json:/opt/firebase/firestore.indexes.json
- ./firestore.rules:/opt/firebase/firestore.rules
- ./docker/firebase/bin/:/root/.cache:cached
- ./docker/firebase/config/:/root/.config:cached
ports:
- 9099:9099 # Firebase Authentication
- 5001:5001 # Clound Functions
- 8080:8080 # Cloud Firestore
- 9000:9000 # Realtime Database
- 5050:5050 # Firebase Hosting
- 8085:8085 # Cloud Pub/Sub
- 9199:9199 # Cloud Storage
- 4000:4000 # Emulator Suite UI
working_dir: /opt/firebase
command: firebase emulators:start
tty: true
初期設定
初回実行時は以下の初期設定を行って、firebaseの設定を行う。
- firebaseログイン
- firebase初期化
1.firebaseログイン
docker compose run --rm firebase firebase login --no-localhost
や
Docker内で、firebase login --no-localhost
を実行してログインする。
(実際のfirebaseプロジェクトが作成済であることが前提、権限があるアカウントにてログイン)
2.firebase初期化
docker compose run --rm firebase firebase init
や
Docker内で、firebase init
を実行して初期化する。
対話形式で、設定を聞かれるので、Emulatorを選択、port番号は設定済みで、最後にエミュレータをダウンロードするか聞かれるので、インストール。
binフォルダ配下にエミュレータがインストールされる
起動
初期設定後、docker compose up
等でコンテナ起動するとlocalhost:4000でエミュレータ画面にアクセスできるようになります。
アプリケーションから接続
エミュレータが起動したので、簡単なテストを実装しました。
Firebase Admin SDKを利用していますので、詳細はそちらをご覧いただければと思います。
ポイントとしては以下2点
- エミュレータにデータクリアの機能が備わっているため、テストごとTearDownを行うことで、クリーンな状態で各種テストが実行が可能
- 環境変数
FIRESTORE_EMULATOR_HOST
の設定有無によって、エミュレータ接続するかどうかが決まるため、プロダクションコードに大きな変更を加えることなく、テスト可能
import (
"context"
"fmt"
"os"
"cloud.google.com/go/firestore"
"google.golang.org/api/option"
)
func getFirestore(ctx context.Context) (*firestore.Client, error) {
sa := option.WithCredentialsFile(os.Getenv("GOOGLE_APPLICATION_CREDENTIALS"))
projectId := os.Getenv("PROJECT_ID")
client, err := firestore.NewClient(ctx, projectId, sa)
if err != nil {
fmt.Println(err)
}
return client, err
}
func GetDocument(c, docId string) (*firestore.DocumentSnapshot, error) {
ctx := context.Background()
client, err := getFirestore(ctx)
// コレクション検索
snapshot, err := client.Collection(c).Doc(docId).Get(ctx)
if err != nil {
fmt.Println(err)
}
return snapshot, err
}
func UpsertDocument(c, docId string, data interface{}) error {
ctx := context.Background()
client, err := getFirestore(ctx)
if err != nil {
return err
}
_, err = client.Collection(c).Doc(docId).Set(ctx, data)
if err != nil {
return err
}
return nil
}
import (
"fmt"
"net/http"
"os"
"testing"
"github.com/mobility-api/middleware"
"github.com/mobility-api/model"
"github.com/stretchr/testify/suite"
)
type Companies struct {
suite.Suite
}
func (suite *Companies) SetupTest() {
//初期データ投入
for i, v := range []string{"sample1", "sample2", "sample3"} {
i++
middleware.UpsertDocument("companies", v, map[string]interface{}{"name": fmt.Sprintf("name%d", i)})
}
}
func (suite *Companies) TearDownTest() {
//全データ削除
url := fmt.Sprintf("http://%s/emulator/v1/projects/%s/databases/(default)/documents", os.Getenv("FIRESTORE_EMULATOR_HOST"), os.Getenv("PROJECT_ID"))
req, _ := http.NewRequest(http.MethodDelete, url, nil)
client := http.Client{}
client.Do(req)
}
func TestCompanies(t *testing.T) {
os.Setenv("FIRESTORE_EMULATOR_HOST", "localhost:8080")
os.Setenv("PROJECT_ID", "sample-project")
suite.Run(t, &Companies{})
}
func (suite *Companies) TestCompanies_GetCompany() {
docID := "sample1"
actual, err := middleware.GetDocument("companies", docID)
suite.Nil(err)
suite.Equal(&model.Company{
DocId: docID,
Name: "name1",
}, actual)
}
まとめ
Firebaseエミュレータを使用して、起動・アプリケーションからのテストまで作成しました。
Firestore以外にも各種エミュレータを使用できるため、ローカルでの動作確認には、非常に役に立つのではないかと思いました。