Open5

【Firebase】Firestore x Functions ローカル開発環境の整備

heyhey1028heyhey1028

アプローチ

  • onCallとtriggerでテストの仕方は異なる?
  • onCallメソッドの場合、
    • Emulatorを使わず、単体テストで担保
      (functionsの単体テスト用SDKfirebase-functions-testが存在 )
    • Emulatorを使って、curlなどでPOSTしながら開発
    • Emulatorを起動したあとにjestで対象のテストコードを実行

参考

https://qiita.com/iridon0920/items/f5d50f39350fbc661ae6
https://zenn.dev/nananaoto/articles/9bff8b9eca891656c110
https://zenn.dev/masumomo/articles/d960605d46ee96

heyhey1028heyhey1028

Firestoreからexport ➡︎ emulatorにimport

  1. firebaseにログイン
$ firebase login
  1. 使うprojectを指定
$ firebase projects:list
$ firebase use <your-project-name>
  1. gcloud cliでfirestoreのexport 参考
    firestoreのデータは選択したfirebase projectのcloud storageに排出されます
$ gcloud firestore export gs://<your-project-name>.appspot.com/<your-chosen-folder-name>
  • <your-project-name>にはexportデータの排出先のfirebase project名を指定
  • <your-chosen-folder-name>にはexportデータの排出先フォルダ名を指定
  • 特定のコレクションを指定したい場合は、--collection-ids パラメータで指定
$ gcloud firestore export gs://[BUCKET_NAME] --collection-ids=[COLLECTION_ID_1],[COLLECTION_ID_2]
  • 指定したコレクション ID を持つすべてのコレクションとサブコレクション(任意のパス上)が含まれる
  1. exportデータをコピー
    作業中のfunctionsディレクトリにexportデータをコピー
$ cd functions
$ gsutil -m cp -r gs://<your-project-name>.appspot.com/<your-choosen-folder-name> <コピーしたファイルを置くディレクトリ>
  1. exportデータをimportしてemulatorを起動
$ firebase emulators:start --import <ファイルを置いたディレクトリ>/<your-choosen-folder-name>
heyhey1028heyhey1028

VScode拡張 「Rest client」でFunctionsをテストする

REST ClientでHTTPリクエスト送信

  • VS codeのextensionでHTTPリクエストを簡単に記述・実行する事ができる
  • POSTMANみたいなツール
  • fileに特に拡張子は必要なく、scriptにそのままリクエストメソッド、エンドポイント、ヘッダー、ボディを記載
  • 記載すると上に「Send Request」のボタンが表示され、押下するとAPIリクエストを投げてくれる

参考

https://qiita.com/toshi0607/items/c4440d3fbfa72eac840c

注意点

INVALID ARGUMENTでfunctionsが叩けない

現象:emulator立ち上げ時に記載されているfunctionsのエンドポイントをcurlで叩いても"INVALID ARGUMENT"と出て実行されない

原因:

  • functionsのonCallはhttps.onCallのプロトコル仕様に従わなくてはいけない
  • 具体的にはヘッダーにContent-Type:application/jsonが必須
  • またなぜかGETではなく、POSTでリクエストしなければならない
  • そして本体にdataフィールドが必須
  • これらが不足している為、onCallのfunctionsがcurlでは呼べなかった

解決策:Content-Typeを指定し、POSTでリクエストする

$ curl -X POST -H "Content-Type: application/json" -d @- <エンドポイント>

VSCodeのextension REST Client を使って、header、Bodyを記載し、POSTでリクエストする

参考:https://qiita.com/qrusadorz/items/f1d71e591a598d30ab9e

Rest Clientでのエラー

  • 現象:REST Clientを使ってHTTPリクエストを実行した所、下記のエラーが表示
Header name must be a valid HTTP token ["{"] 
  • 原因:headerとbodyの記述の間に空行を入れないといけなかったらしい
BAD
POST http://localhost:5001/hogehoge
Content-Type: application/json
{
    "data":{}
}
GOOD
POST http://localhost:5001/hogehoge
Content-Type: application/json

{
    "data":{}
}

参考:https://qiita.com/hiro5963/items/b55c125b6400cbaa2f5d

Typescript使っている場合

  • 現象:emulatorが起動されている状態でfunctionsを更新してもemulatorに反映されない
  • 原因: typescriptでfunctionsを記述していた為、即時反映されなかった
    emulatorが見ているのはコンパイル後のindex.jsである為、変更後npm run build でindex.jsを再生成する必要があった
    参考:https://github.com/firebase/firebase-tools/issues/2823#issuecomment-728137097
heyhey1028heyhey1028

Firebase Test SDK

firebase-functions-test is a testing library for Firebase Cloud Functions. It allows you to write unit tests for your Cloud Functions by providing mock values for the request and response objects that are typically passed to an HTTP Cloud Function. This can help you ensure that your Cloud Functions are working correctly and returning the expected results.

To use firebase-functions-test, you will need to install it as a dependency in your project:

$ npm install --save-dev firebase-functions-test

Here is an example of how you might use firebase-functions-test to test an HTTP Cloud Function:

const firebase = require('firebase-admin');
const test = require('firebase-functions-test')();

const myFunction = require('../index').myFunction;

test.mockConfig({
  myAPIKey: '12345678'
});

beforeAll(() => {
  firebase.initializeApp();
});

test('myFunction should return hello world', async () => {
  const req = {};
  const res = {
    send: jest.fn().mockReturnValue('Hello, World!')
  };

  await myFunction(req, res);
  expect(res.send).toHaveBeenCalledWith('Hello, World!');
});

afterAll(() => {
  test.cleanup();
});

Q. do i need to turn on firebase emulator suite to test firebase functions that manipulates firebase firestore?

It is not necessary to use the Firebase Emulator Suite to test Cloud Functions that manipulate Cloud Firestore. You can use the firebase-functions-test library and mock the Cloud Firestore instance to test your functions.

Here is an example of how you might use firebase-functions-test to mock the Cloud Firestore instance and test a Cloud Function that adds a document to a collection:

const firebase = require('firebase-admin');
const test = require('firebase-functions-test')();

const myFunction = require('../index').myFunction;

test.mockConfig({
  myAPIKey: '12345678'
});

beforeAll(() => {
  firebase.initializeApp();
});

test('myFunction should add a document to the "users" collection', async () => {
  // Create a mock firestore instance
  const firestore = test.firestore;
  firestore.autoFlush();

  // Mock the "users" collection
  const users = firestore.collection('users');

  // Set up the request and response objects
  const req = {
    body: {
      name: 'Alice'
    }
  };
  const res = {
    send: jest.fn().mockReturnValue('Document added')
  };

  // Call the function with the mock request and response objects
  await myFunction(req, res);

  // Check that the function added a document to the "users" collection
  const doc = await users.doc('alice').get();
  expect(doc.data()).toEqual({ name: 'Alice' });
});

afterAll(() => {
  test.cleanup();
});