Firebase Admin Node.js SDKによるメッセージ送信処理のユニットテストをJestで書いてみた

2022.06.06

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、Firebase Admin Node.js SDKによるメッセージ送信処理のユニットテストをJestで書いてみました。

やってみた

環境

$ npm ls typescript jest firebase-admin --depth=0
hoge-project@0.1.0 /Users/wakatsuki.ryuta/projects/cm-rwakatsuki/hoge-project
├── firebase-admin@10.2.0
├── jest@26.6.3
└── typescript@3.9.10

テスト対象コード

Firebase Admin Node.js SDKで、Firebase Cloud Messagingによるメッセージ送信を行う処理です。

send-firebase-message.ts

import * as firebaseAdmin from 'firebase-admin';

const FIREBASE_PROJECT_ID = process.env.FIREBASE_PROJECT_ID || '';
const FIREBASE_CLIENT_EMAIL = process.env.FIREBASE_CLIENT_EMAIL || '';

export const sendMessage = async (
  firebasePrivateKey: string,
  registrationToken: string,
  messageTitle: string,
  messageBody?: string,
) => {
  if (firebaseAdmin.apps.length === 0) {
    firebaseAdmin.initializeApp({
      credential: firebaseAdmin.credential.cert({
        projectId: FIREBASE_PROJECT_ID,
        clientEmail: FIREBASE_CLIENT_EMAIL,
        privateKey: firebasePrivateKey,
      }),
    });
  }

  const params = {
    notification: {
      title: messageTitle,
      body: messageBody,
    },
    token: registrationToken,
  };

  await firebaseAdmin.messaging().send(params);
};

テストコード

前述のコードに対するテストコードは次のように書きました。

test/send-firevase-message.test.ts

import * as firebaseAdmin from 'firebase-admin';

import { sendMessage } from '../send-firebase-message';

test('sendMessage', async (): Promise<void> => {
  (firebaseAdmin.initializeApp as jest.Mock) = jest
    .fn()
    .mockReturnValue(undefined);
  (firebaseAdmin.messaging().send as jest.Mock) = jest
    .fn()
    .mockReturnValue(undefined);

  await sendMessage('dummyPrivateKey', 'dummyRegistrationToken', 'dummyTitle');

  //initializeAppの呼び出し回数を評価(モック時に呼び出し済みのため期待値は0)
  expect(firebaseAdmin.initializeApp).toBeCalledTimes(0);

  //messaging()の呼び出し回数と引数を評価
  expect(firebaseAdmin.messaging().send).toBeCalledTimes(1);
  expect(firebaseAdmin.messaging().send).toBeCalledWith({
    notification: {
      title: 'dummyTitle',
      body: undefined,
    },
    token: 'dummyRegistrationToken',
  });
});
  • firebaseAdmin.initializeAppのモックの戻り値にfirebaseAdmin.initializeApp()を指定しているため、テスト対象のモジュール呼び出し時にはデフォルトアプリは既に作成された扱いとなっており、if分岐内は実行されません。

テストを実行するとすべてPASSしました!

$ npx jest
 PASS  test/send-firevase-message.test.ts (5.566 s)
  ✓ sendMessage (15 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.63 s, estimated 6 s
Ran all test suites.

実行が失敗するパターン

firebaseAdmin.initializeAppmockReturnValueundefinedとした場合、テスト対象コードのif分岐内の処理は実行されるでしょうか。

test/send-firevase-message.test.ts

import * as firebaseAdmin from 'firebase-admin';

import { sendMessage } from '../send-firebase-message';

test('sendMessage', async (): Promise<void> => {
  (firebaseAdmin.initializeApp as jest.Mock) = jest
    .fn()
    .mockReturnValue(undefined);
  (firebaseAdmin.messaging().send as jest.Mock) = jest
    .fn()
    .mockReturnValue(undefined);

  await sendMessage('dummyPrivateKey', 'dummyRegistrationToken', 'dummyTitle');

  //中略
});

テストを実行してみると、そもそも次行のfirebaseAdmin.messaging().sendのモック時にデフォルトアプリが無いためエラーとなりました。

$ npx jest
 FAIL  test/send-firevase-message.test.ts (5.531 s)
  ● sendMessage

    The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services.

       7 |     .fn()
       8 |     .mockReturnValue(undefined);
    >  9 |   (firebaseAdmin.messaging().send as jest.Mock) = jest
         |                  ^
      10 |     .fn()
      11 |     .mockReturnValue(undefined);
      12 |

      at FirebaseAppError.FirebaseError [as constructor] (node_modules/firebase-admin/lib/utils/error.js:44:28)
      at FirebaseAppError.PrefixedFirebaseError [as constructor] (node_modules/firebase-admin/lib/utils/error.js:90:28)
      at new FirebaseAppError (node_modules/firebase-admin/lib/utils/error.js:125:28)
      at AppStore.Object.<anonymous>.AppStore.getApp (node_modules/firebase-admin/lib/app/lifecycle.js:67:19)
      at FirebaseNamespaceInternals.Object.<anonymous>.FirebaseNamespaceInternals.app (node_modules/firebase-admin/lib/app/firebase-namespace.js:54:33)
      at FirebaseNamespace.Object.<anonymous>.FirebaseNamespace.app (node_modules/firebase-admin/lib/app/firebase-namespace.js:321:30)
      at FirebaseNamespace.Object.<anonymous>.FirebaseNamespace.ensureApp (node_modules/firebase-admin/lib/app/firebase-namespace.js:335:24)
      at FirebaseNamespace.fn (node_modules/firebase-admin/lib/app/firebase-namespace.js:131:30)
      at Object.<anonymous> (test/send-firevase-message.test.ts:9:18)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        5.597 s, estimated 6 s
Ran all test suites.

おわりに

Firebase Admin Node.js SDKによるメッセージ送信処理のユニットテストをJestで書いてみました。

AWS SDKとは勝手が異なるためモックの仕方でとても試行錯誤をしました。もっと良いテスト方法があれば教えて頂きたいです。

以上