Back to tech

Firebase Realtime Database を Jest でモック化してテストコードを書いてみる

3 min read
Table of Contents
www.codementor.io
www.codementor.io

TypeScript で Firebase の Realtime Database を使う機会がありました。

リファレンスを見ながら色々試行錯誤してなんとか実装をしました。

実装し終えてユニットテストを書く工程に入りました。

テストは、データベースを書き込む関数 と 読み込む関数が動作するかのテストを行いたいと思っていました。 (データベースに書き込めている/読み込まれているか までは対象外。)

そのため、Realtime Database を使うことできるモジュールの mock 化が必要になります。

この mock 化がなかなか大変だったのでやり方を記載します。

前提

環境

  • TypeScript

    • “^3.8.0”
  • Jest

    • “^27.4.7”,
  • firebase-admin(realtime database を使うためのモジュール)

    • “^9.8.0”

実装

テストしたいコード

readtime database に 書き込む関数(writeDatabase())と 読み込む関数(readDatabase()) があるとします。

この2つの関数をテストしたいとします。

import { app } from "firebase-admin";

// write
export const writeDatabase = async (
  admin: app.App,
  data: string): Promise => {
  try {
    const ref = admin.database().ref("parent");
    await ref.child("child").set(data);
  } catch (error) {
    throw new Error("error");
  }
  return "success";
};

// read
export const readDatabaset =
  async (admin: app.App): Promise<{status:string, data:string} > => {
    let response;

    try {
      const ref = admin.database().ref("parent");
      response = await ref.child("child").get().then((snapshot) => {
        if (snapshot.exists()) {
          return snapshot.val();
        }
        // no data
        return [];
      });
    } catch (error) {
      throw new Error("error");
    }

    return {status: "success", data: response};
};


// main
export const main = (async()=>{
  const admin = initializeApp();

  const writeResponse = await writeDatabase(admin, 'test');
  console.log(writeResponse);

  const readResponse = await readDatabaset(admin);
  console.log(readResponse.data);
}); 

mockの作成

firebase-admin を mock 化します。

細かい話は置いといて結論を書きます。

jest.mock('firebase-admin', () => {
  const snapshot = { val: () => "mock", exists: () => true }; // データベースの読み込みがあった場合は "mock" という文字列を返す。適宜書き換えてください。

  return {
    initializeApp: () => ({
      database: jest.fn().mockReturnValue({
        ref: jest.fn().mockImplementation(() => ({
          child: jest.fn().mockImplementation(() => ({
            set: jest.fn(),
            get: jest.fn().mockImplementation(() => Promise.resolve(snapshot),
            )
          })),
        })),
      }),
    }),
  }
});

jest.fn() を使って mock 化していくという感じです。

ユニットテストサンプル

上記に書いたテストしたいコードをテストするためのコード一例を貼っておきます。

import { initializeApp } from 'firebase-admin';
import { readDatabase, writeDatabase, } from './path/to/module';

jest.mock('firebase-admin', () => {
  const snapshot = { val: () => 'mock', exists: () => true };

  return {
    initializeApp: () => ({
      database: jest.fn().mockReturnValue({
        ref: jest.fn().mockImplementation(() => ({
          child: jest.fn().mockImplementation(() => ({
            set: jest.fn(),
            get: jest.fn().mockImplementation(() => Promise.resolve(snapshot),
            )
          })),
        })),
      }),
    }),
  }
});

describe("writeDatabase", () => {
  afterEach(() => jest.restoreAllMocks());
  it("データベースに書き込めること", async () => {
    const admin = initializeApp();
    const res = await writeDatabase(admin, 'test');
    const expected = "success"
    expect(res).toBe(expected);
  });
});

describe('readDatabase', () => {
  afterEach(() => jest.restoreAllMocks());
  it('データベースから読み込めること', async () => {
    const admin = initializeApp();
    const res = await readDatabase (admin);
    const expected = { status: "success", data: "mock" };

    expect(res).toMatchObject(expected);
  });
});

まとめ

firebase-admin の mock化はどうすればよいのかわからず3日間程度消費してしまいました。

とりあえずテストはできるようになって良かったです。

同じ問題に直面した人の解決につながれば幸いです。

参考