Firebase EmulatorでCloudFunctionsとFirestoreを起動し、APIテストを実行する方法
はじめに
firestoreへの処理が正しいかテストをする際、実際のプロジェクトではなく、ローカルの閉じた環境内で行いたいことがあると思います。
そこで今回、CloudFunctionsとFirestoreをemulatorで立ち上げ、jestでAPIを投げリクエストとレスポンスが正しいかどうかのテストを行いました。
方法
CloudFunctions
CloudFunctionsでfirestoreへのCRUD処理を1つずつ作成しています。
テストではCUDのエンドポイントを叩き、Rのエンドポイントでレスポンス値が想定通りであるかの確認をしています。
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
const db = admin.firestore();
export const createDocument = functions.https.onRequest(async (req, res) => {
await db.collection("test").doc("abc").set({a: 123});
res.send();
});
export const updateDocument = functions.https.onRequest(async (req, res) => {
await db.collection("test").doc("abc").update({a: 234, b: 345});
res.send();
});
export const deleteDocument = functions.https.onRequest(async (req, res) => {
await db.collection("test").doc("abc").delete();
res.send();
});
export const getDocument = functions.https.onRequest(async (req, res) => {
const data = await (await db.collection("test").doc("abc").get()).data();
res.send(data);
});
emulatorの立ち上げ
設定は公式ドキュメントを見るのが一番わかりやすいです。
自分が試した時の設定は下記です。
{
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
},
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
},
"storage": {
"rules": "storage.rules"
},
"emulators": {
"auth": {
"port": 9099,
"host": "localhost"
},
"functions": {
"port": 5001,
"host": "localhost"
},
"firestore": {
"port": 8089,
"host": "localhost"
},
"ui": {
"enabled": true
}
},
"remoteconfig": {
"template": "remoteconfig.template.json"
}
}
設定ファイルを作成したら、 firebase emulators:start
を実行することでemulatorsが立ち上がります。
ui.enabled
を true
にしているとFirebase Emulator Suiteを確認することができます。
こちらで今起動しているサービスを確認できます。また、こちらのコンソールから手動でデータ作成や削除なども行えます。
tips
emulatorsは終了するとデータは削除され、再度起動した際は空の状態になります。
ただ、起動時に初期データを入れておきたい。また、再起動してもデータを使い回したいということもあると思います。
emulatorsはそういう時のオプションを用意しています。--import
と --export-on-exit
を使用することで永続化可能です。
firesoreの場合は、実際のプロジェクトからデータをエクスポートして、emulatorにインポートすることも可能です。
テストの実行
emulatorsで起動したCloudFunctionsはデフォルトで次のようなエンドポイントで実行可能です。
https://localhost:$ポート番号/$PROJECT/us-central1/$関数名
そのため、jestでは単純にAPIを投げて動作確認することができます。
import axios from 'axios'
describe("test", () => {
describe('createDocument APIを実行のテスト', () => {
beforeAll(async() => {
await axios.post('http://localhost:5001/firebase-tutorial-suzuki/us-central1/createDocument');
})
test('getDocumentで想定通りのレスポンスが返ってくる', async() => {
const response = await axios.get('http://localhost:5001/firebase-tutorial-suzuki/us-central1/getDocument');
expect(response.data).toEqual({a: 123})
})
})
describe('updateDocument APIを実行のテスト', () => {
beforeAll(async() => {
await axios.post('http://localhost:5001/firebase-tutorial-suzuki/us-central1/updateDocument');
})
test('getDocumentで想定通りのレスポンスが返ってくる', async() => {
const response = await axios.get('http://localhost:5001/firebase-tutorial-suzuki/us-central1/getDocument');
expect(response.data).toEqual({a: 234, b: 345})
})
})
describe('deleteDocument APIを実行のテスト', () => {
beforeAll(async() => {
await axios.post('http://localhost:5001/firebase-tutorial-suzuki/us-central1/deleteDocument');
})
test('getDocumentで想定通りのレスポンスが返ってくる', async() => {
const response = await axios.get('http://localhost:5001/firebase-tutorial-suzuki/us-central1/getDocument');
expect(response.data).toEqual("")
})
})
});
おわりに
emulatorを使ったテストは以外と簡単でした。API界面でin/outのテストもあれば、リファクタリングするときも安心できそうです。
今回はcloudfunctionsとfirestoreだけでしたが、Authenticationを絡めた認証周りも調査してみたいです。
emulatoreを動かすにはJavaが必要なので、CI上で乗せる所も確認したいです。
また、Cloud Functions は単体テストで firebase-functions-test
というものもあるようです。
こちらも時間を見つけて確認してみたいと思います。
(やってもやっても、分からないことが増え、やりたいことも増えますね)
Discussion
いやぁ、マジで助かりました。
丁寧な記事を有難うございます!