LoginSignup
21

More than 5 years have passed since last update.

firebase-functions-test を使って Firestore の offline テストを書いてみる

Posted at

The Firebase Blog: Launching Cloud Functions for Firebase v1.0 で、 Easier unit testing という項目がありました。
firebase/firebase-functions-test という npm が公開されたので、これは実際のところ何ができるのか調べてみました。(Firestore 目線での調査です。)

手探りでやっているため、間違いがあったらマサカリ歓迎です。

firebase-functions-test/firestore.ts

firebase-functions-test/firestore.ts の interface はこれだけです。
単に DocumentSnapshot を作るだけの Utility ですね。

import { Change } from 'firebase-functions';
import { firestore, app } from 'firebase-admin';
export interface DocumentSnapshotOptions {
    readTime?: string;
    createTime?: string;
    updateTime?: string;
    firebaseApp?: app.App;
}
export declare function makeDocumentSnapshot(
    data: {
        [key: string]: any;
    }, 
    refPath: string, options?: DocumentSnapshotOptions
): any;
export declare function exampleDocumentSnapshot(): firestore.DocumentSnapshot;
export declare function exampleDocumentSnapshotChange(): Change<firestore.DocumentSnapshot>;

内部コードを読んでみましたが、他に利用できるコードはなさそうです。
offline テストができると blog には書いてありましたが、 offline で動作するデータベースが提供されたのではなく、テストヘルパーが提供されたという認識が正しいです。

Realtime Database のテストサンプルを Firestore で書き直す

functions-samples/test.offline.js という、 RealtimeDB の offline テストのサンプルがあります。
しかし Firestore のサンプルはないので、 RealtimeDB のサンプルを Firebase に書き換えてみます。

ここでは、 jest + TypeScript でテストを書いています。

テスト対象の Function

export const addMessage = functions.https.onRequest((req, res) => {
  const original = req.query.text
  return admin.firestore().collection('messages').add({ original: original }).then((writeResult) => {
    return res.json({ result: `Message with ID: ${writeResult.id} added.` })
  })
})

テストコード

import * as admin from 'firebase-admin'
import * as fft from 'firebase-functions-test'
const ft = fft()
import * as sinon from 'sinon'
import { makeUppercase } from './makeUppercase'

describe('offline', () => {
  describe('addMessage', () => {
    it('should return a 303 redirect', async () => {
      const collection = 'messages'
      const addParam = { original: 'input' }
      const firestoreStub = sinon.stub()
      const refStub = sinon.stub()
      const addStub = sinon.stub()

      sinon.stub(admin, 'firestore').get(() => firestoreStub)
      firestoreStub.returns({ collection: refStub })
      refStub.withArgs(collection).returns({ add: addStub })
      addStub.withArgs(addParam).returns(Promise.resolve({ ref: 'new_ref' }))

      const req = { query: { text: 'input' } } as any
      const res = {
        redirect: (code, url) => {
          expect(code).toBe(303)
          expect(url).toBe('new_ref')
        }
      } as any

      await addMessage(req, res)
    })
  })
})

Firestore で書き直してみた感想としては以下で、 私は firebase-functions-test を使って本番の Project の offline テストを書くのは諦めました。

  • admin.firestore() を stub して、 collection も stub して... という感じで、結局全てを stub していかなければならない
  • addMessage は簡単な Function なので良いが、もっと複雑なものは大量の stub が必要
    • Functions 内で複数の Document を触ったり更新する場合は、それを全て stub しなければならない
    • transaction, batch などを stub し続けていくのは現実的ではないし、結局内部コードのほとんどが stub になるのでテストしていると言えるか微妙
  • これなら functions-samples/test.online.js のように実際に online でデータを書き込む方法のが実用的
  • (functions-samples/test.offline.js では sinon.stub(admin, 'initializeApp') しているが、今回はしなくても動いた)

おわり

offline でテストを書くなら、現時点では soumak77/firebase-mock が良い気がします。これは offline で動作する Firestore Database を提供してくれます、一部バグがありますが。(Firestore だけでなく、 RealtimeDB などもローカルで動かせます。)
soumak77/firebase-mock についてはまた別で記事を書こうと思います。

makeUppercase のテストも gist に置いておきます。

最初にも書きましたが、他に参考になるテストコードサンプルなどがなく気合いで書いたので、間違っていたらコメントいただけると助かります。

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
21