7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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 さん、実装を疑ってごめんな……

7
3
0

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
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?