ローカル環境でCloud Functions for Firebase（特にCallable関数）の動作テストをする


functionsって便利なのですが、真面目に開発してるとデプロイの待ち時間が苦痛になってきます。
ローカルである程度動作テストしてからDeployしたいものです。また、チェック用のコードを書くのもめんどくさいときはインタラクティブShellが利用できるので試してみます。


## 前提

* 既にfirebaseのプロジェクトはある状態
* Firestoreとか認証とかはサーバ側のものを利用する（デプロイの時短がメインの目的なので）
* ここで言うテストとは単体テストとかじゃなく動作テスト

## 基本

まず、普通のWebAPIの場合を見てみます。

### 準備

作業場所を作り、firebase initの後、functionsの中で作業します。

```bash
mkdir functions-test
cd functions-test

firebase init functions

...省略

cd functions
```

>私は既存プロジェクトを選んで、言語はJSです。

### テンプレートのhelloWorldをコメントインし実行

index.jsにテンプレートとして記述されているhelloWorldをコメントインして利用してみます。

```js:index.js
const functions = require('firebase-functions');

exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});
```

ローカルで実行してみます。

```bash
firebase serve --only functions
```

ローカルに展開され、実行用のURLが表示されます。

```bash
✔  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!と返すだけの関数を記述します。

```js:index.js
const functions = require('firebase-functions');

exports.helloOnCall = functions.https.onCall((data, context) => {
    return "Hello OnCall!";
});
```

記述して保存すると自動展開されます（watching状態なら）。
一度、serveを停止している場合は、再びfirebase serveします。

```bash
✔  functions[helloOnCall]: http function initialized (http://localhost:5000/test-fc01e/us-central1/helloOnCall).
```

### クライアント側

では、APIを呼び出すクライアント作成してみます。作業場所はfunctionsの中を想定しています。
firebaseの機能を利用するためfirebaseをインストールします。また、呼び出しコードを記述するcallFunciton.jsを作成します。

```bash
npm install --save firebase
touch callFunction.js
```

実装は下記のような感じ。ポイントは、

```
functions.useFunctionsEmulator("http://localhost:5000");
```

と記述することでローカルのFunctionsを見るようにします。

```js:callFunction.js
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状態である必要があります。

```bash
node callFunction.js

{ data: 'Hello OnCall!' }
```

## 応用１：Firestoreを利用する関数の利用

多くの場合、Functionsの処理ではDB(firestore)を利用することになりますが、その場合はどうしたらいいでしょうか。
連携パターンとしては下記が考えられます。

1. ローカルのFunctionからローカルのFirestoreを利用する
2. ローカルのFunctionからFirebaseのFirestoreを利用する


（私なりの）結論から言えば、1.はあまりメリットが無いため、2.の想定した利用方法を見てみます（ローカルを利用することはできます）。
なお、ローカルでFunctions, Firestoreを利用する方法は[こちら](https://qiita.com/zaburo/items/3d6a4a5b4edc8fcecca9)で簡単に書いています。

下記ではitemsコレクションにデータを書き込み、続いて、レコード件数を返す機能をサンプル実装しています。
１つ注意があるとすれば、ローカルのFunctionからfirestoreを利用するためには（普通にnodeスクリプトを書くのと同様に）認証情報を記述する必要があることです。

### サーバ側

```js:index.js
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;

});
```

上記ではコード中に認証情報を記述していますが、

```bash
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"
```

とすることで環境変数に設定することもできます。サーバ側にソースを合わせるなら環境変数の方がいいかもしれません。

>削除はunset GOOGLE_APPLICATION_CREDENTIALSとする。

### クライアント側

callFunction.jsはそのままで実行してみます。

```bash
node callFunction.js

{ data: 1 }
```

## 応用２：インタラクティブShellの利用

いちいちテスト用のコードを書くのがめんどくさいときがあります。そんな時はインタラクティブShellを利用することができます。

下記のように、firebase functions:shellとすると、インタラクティブShellが起動します。

```bash
firebase functions:shell

✔  functions: Emulator started at http://localhost:5000
i  functions: Loaded functions: helloWorld, helloOnCall
firebase > 
```

ここで、onRequest関数を実行すると、下記のようになります。

```bash
firebase > helloWorld();
Sent request to function.
firebase > 
RESPONSE RECEIVED FROM FUNCTION: 200, hello
```

また、onCall()関数においてもインタラクティブにテストすることができます。

```bash
firebase > helloOnCall({});
Sent request to function.
firebase > 
RESPONSE RECEIVED FROM FUNCTION: 200, {
  "result": 5
}
```

便利。