Posted at

mocha + @firebase/testingでハマった時にはタイムアウト時間を見直す

Firebaseで開発をしていると、FirestoreのルールやCloud Functionsとの連携など、細々とした設定を安全にテストしたくなりますよね。

そういった用途のために、FirebaseではFirestoreやCloud Functionsのエミュレータが用意されています。

また、上記のエミュレータをテスティングフレームワークから叩くためのヘルパーとして、 @firebase/testing というNode.js向けライブラリも提供されています。

実用の仕方はこちらのSOの回答がわかりやすかったです。


なんか動かない

さて、これは良いツールを知ったと思い、mochaの中で動かすことにしました。前述の記事の内容を組み合わせて、次のようなテストコードを書いてみました。


hoge.test.ts

import assert = require("assert");

import * as firebase from "@firebase/testing";
import "mocha";

const FIRESTORE_PROJECT_ID = "my-project";

function authedApp(auth?: object) {
return firebase.initializeTestApp({ projectId: FIRESTORE_PROJECT_ID, auth }).firestore();
}

describe("hoge", () => {
beforeEach(async () => {
await firebase.clearFirestoreData({ projectId: FIRESTORE_PROJECT_ID });
});

afterEach(async () => {
await Promise.all(firebase.apps().map(app => app.delete()));
});

const COLLECTION_NAME = "myCollection";
it("fuga", async () => {
const db = authedApp();

// Manually add item to collection
const ref = await db.collection(COLLECTION_NAME).add({hello: 'World!'});

// Fetch item by id
const resp = await db.collection(COLLECTION_NAME).doc(ref.id).get();

assert(resp.exists);
assert.deepEqual(resp.data(), { hello: 'World!' });
});
})


これを実行すると、次のような結果になりました。

  hoge

1) "before each" hook for "fuga"

0 passing (10s)
1 failing

1) hoge
"before each" hook for "fuga":
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/path/to/hoge.test.ts)

fuga テストが始まる前の beforeEach がタイムアウトして落ちているようです。サンプルでいうと、↓のデータベースをクリアしている部分ですね。

  beforeEach(async () => {

await firebase.clearFirestoreData({ projectId: FIRESTORE_PROJECT_ID });
});

clearFirestoreData が止まってしまったのだと思い、しばらくソースコードを読んでいたりしたのですが、エミュレータ向けのgRPCクライアントの初期化の行で終わるということまでしかわからず、途方にくれていました。


タイムアウトを延ばせばよかった

mocha先生は「2秒も待ったのに結果が出ないじゃないの! きぃぃぃ!!!!」とブチ切れておられました。

Error: Timeout of 2000ms exceeded.

私もそこに引っ張られて「そうだよな……2秒もかけてダメってことは、たぶんどこかで処理が止まったんだよな……」という前提で調査を進めていましたが、ふとこのIssuemocha --timeout=10000 を設定しているのを見て、「あれ、本当に2秒以上かかっているのでは?」と思い直しました。

10秒で済むとは思えなかったので、雑に --timeout=30000 で実行してみます。また、 beforeEach がどのくらいかかっているのかを調べるべく、次のように計測コードを仕込んでみました。

  beforeEach(async () => {

const start = Date.now();
await firebase.clearFirestoreData({ projectId: FIRESTORE_PROJECT_ID });
console.log(`before each time: ${Date.now() - start}`);
});

その結果がこちらになります。

  hoge

before each time: 10089
✓ fuga (10227ms)

1 passing (20s)

やったー通ったー🎉

結局、初期化もテスト自体も10秒以上かかっていました。テスト内容にもよりますが、少なくとも15000ms程度のタイムアウトは設定しておいたほうがよさそうです。


まとめ

冷静に考えてみると、Firebaseのローカルエミュレータへのアクセスを伴うということはE2Eテストなので、時間がかかるのは当然といえば当然でした。

@firebase/testing さん、実装を疑ってごめんな……