functionsって便利なのですが、真面目に開発してるとデプロイの待ち時間が苦痛になってきます。
ローカルである程度動作テストしてからDeployしたいものです。また、チェック用のコードを書くのもめんどくさいときはインタラクティブShellが利用できるので試してみます。
前提
- 既にfirebaseのプロジェクトはある状態
- Firestoreとか認証とかはサーバ側のものを利用する(デプロイの時短がメインの目的なので)
- ここで言うテストとは単体テストとかじゃなく動作テスト
基本
まず、普通のWebAPIの場合を見てみます。
準備
作業場所を作り、firebase initの後、functionsの中で作業します。
mkdir functions-test
cd functions-test
firebase init functions
...省略
cd functions
私は既存プロジェクトを選んで、言語はJSです。
テンプレートのhelloWorldをコメントインし実行
index.jsにテンプレートとして記述されているhelloWorldをコメントインして利用してみます。
const functions = require('firebase-functions');
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
});
ローカルで実行してみます。
firebase serve --only functions
ローカルに展開され、実行用のURLが表示されます。
✔ functions: Emulator started at http://localhost:5000
i functions: Watching "/Users/tamaki/func-test/functions" for Cloud Functions...
✔ functions[helloWorld]: http function initialized (http://localhost:5000/[project-name]/us-central1/helloWorld).
i functions: Beginning execution of "helloWorld"
i functions: Finished "helloWorld" in ~1s
WebAPIなのでブラウザ等に上記URLを入力すれば実行されます。
http://localhost:5000/[project-name]/us-central1/helloWorld
まあ、簡単ですが、WebAPIを何の認証もDB連携もなく実行することはないため、上記だけで動作確認できるケースはあまり多くないです。
onCall()の展開と呼び出し
Firebaseでhttps経由でAPIを呼び出す場合、Callable関数を利用することの方が多いかと思います。
index.jsを書き換えてonCall()関数を記述します。
サーバ側
ただ、Hello OnCall!と返すだけの関数を記述します。
const functions = require('firebase-functions');
exports.helloOnCall = functions.https.onCall((data, context) => {
return "Hello OnCall!";
});
記述して保存すると自動展開されます(watching状態なら)。
一度、serveを停止している場合は、再びfirebase serveします。
✔ functions[helloOnCall]: http function initialized (http://localhost:5000/test-fc01e/us-central1/helloOnCall).
クライアント側
では、APIを呼び出すクライアント作成してみます。作業場所はfunctionsの中を想定しています。
firebaseの機能を利用するためfirebaseをインストールします。また、呼び出しコードを記述するcallFunciton.jsを作成します。
npm install --save firebase
touch callFunction.js
実装は下記のような感じ。ポイントは、
functions.useFunctionsEmulator("http://localhost:5000");
と記述することでローカルのFunctionsを見るようにします。
const firebase = require("firebase");
const firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxx",
authDomain: "xxxxxxxxx.firebaseapp.com",
databaseURL: "https://xxxxxxxxx.firebaseio.com",
projectId: "xxxxxxxxx",
storageBucket: "xxxxxxxxxx.appspot.com",
messagingSenderId: "xxxxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxxxxxxxx",
measurementId: "xxxxxxxxxxxxxx"
};
firebase.initializeApp(firebaseConfig);
const functions = firebase.functions();
functions.useFunctionsEmulator("http://localhost:5000");
const main = async () => {
const helloOnCall = functions.httpsCallable("helloOnCall");
const res = await helloOnCall({});
console.log(res);
}
main();
実行してみます。
当然ですがFunctionsがserve状態である必要があります。
node callFunction.js
{ data: 'Hello OnCall!' }
応用1:Firestoreを利用する関数の利用
多くの場合、Functionsの処理ではDB(firestore)を利用することになりますが、その場合はどうしたらいいでしょうか。
連携パターンとしては下記が考えられます。
- ローカルのFunctionからローカルのFirestoreを利用する
- ローカルのFunctionからFirebaseのFirestoreを利用する
(私なりの)結論から言えば、1.はあまりメリットが無いため、2.の想定した利用方法を見てみます(ローカルを利用することはできます)。
なお、ローカルでFunctions, Firestoreを利用する方法はこちらで簡単に書いています。
下記ではitemsコレクションにデータを書き込み、続いて、レコード件数を返す機能をサンプル実装しています。
1つ注意があるとすれば、ローカルのFunctionからfirestoreを利用するためには(普通にnodeスクリプトを書くのと同様に)認証情報を記述する必要があることです。
サーバ側
const functions = require('firebase-functions');
const admin = require('firebase-admin');
//ローカルで実行するには認証情報が必要
admin.initializeApp({
credential: admin.credential.cert("/path/to/key.json"),
databaseURL: "https://test-fc01e.firebaseio.com"
});
const db = admin.firestore();
//ローカルfirestoreを使う場合
// db.settings({
// host: "localhost:8080",
// ssl: false,
// });
exports.helloOnCall = functions.https.onCall(async (data, context) => {
//write
const res = await db.collection("items").add({ name: "hoge" });
//read
const snapshots = await db.collection("items").get();
const docs = snapshots.docs.map(doc => doc.data());
return docs.length;
});
上記ではコード中に認証情報を記述していますが、
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"
とすることで環境変数に設定することもできます。サーバ側にソースを合わせるなら環境変数の方がいいかもしれません。
削除はunset GOOGLE_APPLICATION_CREDENTIALSとする。
クライアント側
callFunction.jsはそのままで実行してみます。
node callFunction.js
{ data: 1 }
応用2:インタラクティブShellの利用
いちいちテスト用のコードを書くのがめんどくさいときがあります。そんな時はインタラクティブShellを利用することができます。
下記のように、firebase functions:shellとすると、インタラクティブShellが起動します。
firebase functions:shell
✔ functions: Emulator started at http://localhost:5000
i functions: Loaded functions: helloWorld, helloOnCall
firebase >
ここで、onRequest関数を実行すると、下記のようになります。
firebase > helloWorld();
Sent request to function.
firebase >
RESPONSE RECEIVED FROM FUNCTION: 200, hello
また、onCall()関数においてもインタラクティブにテストすることができます。
firebase > helloOnCall({});
Sent request to function.
firebase >
RESPONSE RECEIVED FROM FUNCTION: 200, {
"result": 5
}
便利。