LoginSignup
18
13

More than 5 years have passed since last update.

今日からできるCloud Functions for Firebaseでconsole.logデバッグ風味のオンラインテスト

Posted at
1 / 23

Roppongi.js #2

2018年4月24日


自己紹介

ちきさん

Twitter/GitHub/Qiita: @ovrmrw

市ヶ谷のオプトという会社で働いています。初心者です。

3a2512bb-aa72-4515-af42-1f1721252f39.jpg


今回のGitHubリポジトリ


「Cloud Functions for Firebase」とは

Cloud Functions for Firebase を使用すると、Firebase 機能や HTTPS リクエストによってトリガーされたイベントに応じて、バックエンド コードを自動的に実行できます。コードは Google のクラウドに保存され、マネージド環境で実行されます。独自のサーバーを管理およびスケーリングする必要はありません。

( https://firebase.google.com/docs/functions/?hl=ja より)


何がうれしいのか

詳しくは Cloud Functions で可能な処理 (公式) で。


要するにAWS Lambdaみたいなやつです。 名前が長いのでここから先は Cloud Function と呼びます。


Cloud Functionを書いているときのつらみ

  • 実際にクラウド上で動かしてみないとデータが思い通りに流れているかわかりにくい。
  • とはいえ「コード直して」「デプロイして」「手動でイベント発生させて」「ログ確認して」の繰り返しは精神的にも時間的にもつらい。:innocent:

そこで オンラインテスト

  • 「どうなるはず」を書くより、「実際にどうなったか」を書く方が手っ取り早い。(個人の意見)
  • テストを書くというより、実際に起きた結果を確認しながらアサーションを書いていたらテストを書いていた、みたいな感じ。
  • (推奨されている手法かどうかはわからない)

オンラインテスト


オンラインテストの基本形

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

describe('.....', () => {
  let test;

  beforeEach(() => {
    test = Test(JSON.parse(process.env.FIREBASE_CONFIG));
  });

  it('.....', async () => {
    // Realtime Databaseの場合
    const snapshot = test.database.makeDataSnapshot(value, refPath);
    await test.wrap(your_cloud_function)(snapshot);
  });
});

(ref: https://github.com/firebase/firebase-functions-test )


itの中身をクローズアップ

const snapshot = test.database.makeDataSnapshot(value, refPath);
await test.wrap(your_cloud_function)(snapshot); // Cloud Functionを発火させる。
  • makeDataSnapshot()に値を渡してDataSnapshot型のオブジェクトを作る。
  • wrap()にCloud Functionを渡してイベント発生をシミュレートするWrapped Functionを作り、その引数にDataSnapshotを渡す。
  • これによりCloud Functionがイベント駆動する様子をローカルで再現できる。

オンラインテストの例 (Database => PubSub)

  • DatabaseのonCreateイベントをトリガーにする。
  • Cloud Function発火後、SubscriberがpullしてTopicにメッセージが送られたことをアサーションする。
pubsub.spec.js
import * as Test from 'firebase-functions-test';
import { PubsubHelper } from '../../testing/helpers';
import { pubsub, topicName } from '../../src/pubsub';

jest.setTimeout(1000 * 30);

describe('pubsub', () => {
  let test;
  let pubsubHelper;
  const subscriptionName = 'sample-subscription__test';

  beforeEach(() => {
    test = Test(JSON.parse(process.env.FIREBASE_CONFIG));
    pubsubHelper = new PubsubHelper();
  });

  beforeEach(async () => {
    // initialize testing environment
    await pubsubHelper.deleteSubscription(subscriptionName);
    await pubsubHelper.createSubscription(subscriptionName, topicName);
  });

  it('Topicにメッセージが送られる。', async () => {
    const value = { bar: 1 };

    // trigger
    const snapshot = test.database.makeDataSnapshot(value, '');
    await test.wrap(pubsub)(snapshot);

    // pubsub message assertion
    const messages = await pubsubHelper.pullMessages(subscriptionName);
    expect(messages.length).toBe(1);
    const firstMessage = JSON.parse(messages[0].message.data.toString());
    expect(firstMessage).toEqual(value);
  });
});

(ref: https://github.com/ovrmrw/firebase-cloud-functions-local-testing/blob/master/functions/__tests__/online/pubsub.spec.ts )


オンラインテストの Tips


テスト用のプロジェクトを別に作る

  • テスト用のプロジェクトにはCloud Functionをデプロイしてはいけない。
  • なぜならオンラインテストのとき、イベントに対してローカルのFunctionとデプロイしたFunctionの両方が動いてしまい、まともな結果が得られなくなるから。(これで超嵌った)
  • なのでデプロイせずにテストするためにプロジェクトを別に用意する必要がある。

(ref: Firebaseプロジェクトを本番用とテスト用で使い分けたい)


Cloud Functionのトリガーとなる副作用は(必要なら)自前で発生させる

const snapshot = test.database.makeDataSnapshot(value, refPath);
await snapshot.ref.set(snapshot.val()); // <==(必要なら)事前にDatabaseに書き込む。
await test.wrap(your_cloud_function)(snapshot); // Cloud Functionを発火させる。
  • firebase-functions-test を使うとCloud Functionをトリガーすることはできるが、あくまでもイベントが発生したことをシミュレートするだけで実際にイベントは起きていない。(ちゃんと理解してないだけかも...)
  • 例えばDatabaseのonCreateイベントを発生させることはできるが、実際にDatabaseには書き込まれていない。
  • なのでCloud Functionの中でデータを利用・更新する場合は、事前に書き込みをしておく必要がある。

Jestのタイムアウトを長めにする

jest.setTimeout(1000 * 30);

describe('.....', () => {
  // テスト
});
  • Jestのデフォルトタイムアウトは5秒。
  • オンラインテストはクラウド上で副作用を発生させるので5秒では終わらないことが多い。(特にPubSub)
  • itだけタイムアウト時間を変更してもbeforeEachでタイムアウトしたりするので全体に適用できるjest.setTimeout()を使うと便利。

ちょっと待たないとTerminalにログが残らない (ときがあった)

it('.....', async () => {
  const snapshot = test.database.makeDataSnapshot(value, refPath);
  await test.wrap(your_cloud_function)(snapshot); 
  await new Promise(resolve => setTimeout(resolve, 1000)); // ちょっと待つ
});
  • Cloud Functionを実行した後、ログを全てTerminalに表示させるためにちょっと待つ。
    • (再現できなかったけど待たないとログが残らないときがあった。。。)

まとめ


メリット

  • 「どうなるはず」ではなく「実際にどうなったか」をアサーションしているので、本番に適用しても問題なく動くことが期待できる。
  • 実際に流れている値をconsole.logで確認できる。

デメリット

  • 全てをモックするオフラインテストに比べれば単純に時間がかかる。
  • クラウド上で動作させるので、たまに通るはずのテストが落ちることがある。

うれしいこと

  • 作業効率は上がる、と思う、たぶん。
  • 少なくともログを確認するためにデプロイして手動でイベント発生させるのを繰り返すという不毛な戦いは避けられる。
  • console.logデバッグをする感覚でコードを書ける。

今朝知ったこと ←NEW!


Thanks :raised_hands:

18
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
13