今から始めるCloud Functions for Firebaseテスト入門
FirebaseプロジェクトでのCIについては FirebaseのCI/CDに爆速で入門するでお伝えしました。
しかし、ただCIを実現しただけで満足していませんか?
そこで今回は、更にCIの品質を上げていくため、Cloud Functionsに単体テストを導入していきたいと思います。
こんにちは。
クラウドソリューショングループのwatanabe.tです。
この記事は Firebase Advent Calendar #2 22日目の記事です。
はじめに
FirebaseにはCloud Functions用のFirebase Test SDKが用意されています。
まだv0.1.6までしかリリースされていませんが、様々な機能がサポートされていますので、実際に動かしながら見ていきましょう。
今回使用したソースコードはGitHubにアップロードしてありますので、完成版を見たい人はチェックしてみてください。
また、テストフレームワークにはMocha、アサーションライブラリにはChai、スタブにはSinonを利用しますが、これらの詳しい解説は省略させてもらいます。
詳しい使い方は公式サイトに記載されているので、興味がある人はこの機会に勉強してみてください。
セットアップ
Firebaseのプロジェクトは通常通り作成し、Functionsの機能は有効化しておいてください。
今回は説明の簡易化のためにJavaScriptを使用しますが、それ以外は自由に設定してOKです。
まずは functions
フォルダで以下のコマンドを実行してテストに使用するライブラリをnpmでインストールします。
$ npm install --save-dev firebase-functions-test
$ npm install --save-dev mocha
次に、テストコード用のファイルを配置するため、 functions
フォルダ内に test
フォルダを作成します。
そして、その中にテストコード用のファイルである index.test.js
を作成します。
functions/
├── index.js
└── test/
└── index.test.js
最後に、 functions/package.json
に以下の行を追加しておきます。
こうしておくことで、 npm test
を実行することでこれらのテストを実行することができます。
"scripts": {
"test": "mocha --reporter spec"
}
テスト対象の関数の作成
今回はCloud Firestoreに書き込みがあった際に実行される、以下の関数をテストしてみます。
この関数は posts
ドキュメントに対してデータの新規作成があった際、データ中の文字列のLowerCaseを追加する関数です。
exports.makeLowercase = functions.firestore.document('posts/{postId}').onCreate((snap, context) => {
// Firestoreに書き込まれた元の書き込みを取得
const original = snap.data().original;
const lowerCase = original.toLowerCase();
// 変換後の文字列を非同期(Promise)で書き込み
return snap.ref.update({
converted
});
});
Firebase Test SDKの初期化
先程 firebase-functions-test
をインストールしましたが、テストには2つのモードがあることも説明しておきます。
1. オフラインモード
Firebaseのサービス(RealtimeDB, Firestoreなど)のスタブを自前で用意し、プロジェクトに影響を及ぼさないオフラインの単体テストを作成します。
2. オンラインモード
データベースと実際に通信・書き込みなどが行われ、テスト専用のプロジェクトとやり取りをするテストを作成します。
今回は気軽に単体テストを行うため、オフラインモードで初期化を行います。
以下のコードを先程作成した index.test.js
の先頭に追加してください。
const test = require('firebase-functions-test')();
スタブデータの作成
今回はCloud Firestoreに書き込みがあった際、特定の文字列を変換して保存する関数(バックグラウンド関数)を作成しています。
この場合、オフラインモードでテストを行うためにはカスタムデータを利用して、テストデータがCloud Firestoreに書き込まれた、という状況を作り出す必要があります。
そんな問題を解決するため、今回はSinonを使用してCloud Firestoreへの書き込みをスタブします。
以下のコードを index.test.js
に追加してください。
const setParam = { converted: 'abcdefg' };
const setStub = sinon.stub();
const snap = {
data: function () {
return { original: 'ABCDEFG' }
},
ref: {
update: setStub
}
};
これは、Cloud Firestoreのupdateメソッドとデータをスタブし、処理を置き換えています。
関数の実行
スタブデータの用意ができたところで、テスト対象の関数を実行していきます。
まず、テスト対象の関数を読み込み、ラップします。
以下のコードを index.test.js
の firebase-functions-test
を呼び出している所の下に追加してください。
const targetFunctions = require('../index.js');
const wrappedFunction = test.wrap(targetFunctions);
最後に以下のコードを追加することで、対象の関数にテストデータが渡され、関数が実行されます。
wrappedFunction(snap);
アサーションの作成
SDKの初期化、スタブデータの作成、関数の実行まで完了したので、動作に関するアサーションを作成していきましょう。
今回はSinonを利用しているので、期待通りのパラメータが渡されたらtrueを返すように設定しておきます。
setStub.withArgs(setParam).returns(true);
そして、アサーションは以下のように設定します。
期待通りの動作をして、ちゃんとtrueが返ってくるかを確認しています。
describe('オフラインテスト', () => {
it('makeLowercaseのテスト', () => {
assert.equal(wrappedFunction(snap), true);
});
});
テストの実行
さて、アサーションの作成まで完了し、やっとテストを実行する準備が完了しました。
最初にnpmスクリプトでテストを実行できるように設定をしておいたので、以下のコマンドを実行します。
$ npm test
するとテストが実行され、成功したことが確認できると思います。
今回は関数自体も簡潔なため、すぐに実行が完了するはずです。
オフラインテスト
✓ makeLowercaseのテスト
1 passing (9ms)
最後に
今回は簡易的にCloud Functions for Firebaseのオフラインテストを実施しました。
より本番に近い環境でテストを実施したいのであれば、オンラインテストが選択肢になりますし、Firestoreのエミュレータを利用したテストを実施する、という選択肢もあります。
テスト対象、環境などによって何がベストかは変わってくると思うので、それぞれの特徴を理解した上で適切なテストを実施していきましょう。