🔥

CypressからFirebase emulatorのFirestoreが読み込めないときは(with Angular)

2021/09/08に公開

株式会社 CauchyE エンジニアの @yutaro_elk です。フロントエンドが好きでやってます。
現在は Ionic + Angular + Firebase で NFT 関連のアプリ開発をしています。

CauchyE は技術スタックとして主に Angular + Firebase を使用していますが、Cypress と Firebase + Angular を組み合わせた時に詰まった点があったので残しておきたいと思います。

TL;DR

  • Cypress 経由で Firebase emulator の Firestore にアクセスするとデータが取得できない
  • firebase.firestore.settingsexperimentalForceLongPolling: true にしてあげる必要がある
  • Angular で AngularFire を使っている場合、 上記の内容は NgModule で DI してあげる必要がある

問題

Firebase を使ったアプリケーションを Cypress でテストをする時、Firebase emulator を使用することでテスト環境をクリーンな状態で実行することができて便利です。

ただ、上記の組み合わせで Firestore のデータを Cypress 経由で読み込もうとすると、うまく読み込めません。

解決策

解決策としては firebase を init するあたりで firebase.firestore.settings に オプションとして experimentalForceLongPolling: true を設定してあげます。

firebase.firestore.settings({ experimentalForceLongPolling: true });

テスト環境だけで設定するようにしよう

experimental な設定ということもありますし、experimentalForceLongPolling: true は Cypress 環境でのみ設定するようにしましょう。

たとえば window に Cypress が生えているかどうかでチェックすることができます。

if (window.Cypress) {
  firebase.firestore().settings({ experimentalForceLongPolling: true });
}

おまけ:Angular (+AngularFire) の場合

ここまでは Github の Issue にも直接解決策が載っていたんですが、Angular の場合もうひと手間かかります。

AngularFire v6 (firebase sdk v8)の場合

Angular で Firestore を利用するとき、ライブラリとして AngularFire を使うことが多いんじゃないでしょうか? その場合上記の firebase.firestore.settings はライブラリにラップされ直接アクセスできません。

そのため Angular の DI を使って設定してあげる必要があります。

import { SETTINGS as FIRESTORE_SETTINGS } from '@angular/fire/firestore';

@NgModule({
  providers: [
    {
      provide: FIRESTORE_SETTINGS,
      useValue: { experimentalForceLongPolling: true },
    },
  ],
})
export class AppModule {}

AngularFire から SETTINGS という InjectionToken を FIRESTORE_SETTINGS として import し、providers で useValue を使い設定用のオブジェクト注入する形です。

Cypress 環境かどうかのチェックや、エミュレーターの設定を加えると以下のようになります。

// ...
import {
  USE_EMULATOR as USE_FIRESTORE_EMULATOR,
  SETTINGS as FIRESTORE_SETTINGS,
} from '@angular/fire/firestore';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AngularFireModule.initializeApp(environment.firebase),
    AngularFirestoreModule,
    AngularFireAuthModule,
    FormsModule,
  ],
  providers: [
    // useEmulatorがtrueの場合はFirebase emulatorを使用する
    ...(environment.useEmulator
      ? [
          { provide: USE_AUTH_EMULATOR, useValue: ['localhost', 9099] },
          { provide: USE_FIRESTORE_EMULATOR, useValue: ['localhost', 8080] },
        ]
      : []),
    // Cypress上でのfirestore.settingsをDIする
    ...((window as any).Cypress
      ? [
          {
            provide: FIRESTORE_SETTINGS,
            useValue: { experimentalForceLongPolling: true },
          },
        ]
      : []),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

AngularFire v7 (firebase sdk v9)の場合

AngularFire v7 (firebase sdk v9) になると DI で設定にアクセスするのではなく、firebase のインスタンスに直接アクセスできるようになりました。

const initApp = () => initializeApp(environment.firebase);

@NgModule({
  imports: [
    provideFirebaseApp(initApp),
    provideFirestore(() => {
      const firebase = initApp();
      const firestore = initializeFirestore(firebase, {
        experimentalForceLongPolling: (window as any).Cypress ? true : false,
      });
      if (environment.useEmulator) {
        connectFirestoreEmulator(firestore, 'localhost', 8080);
      }
      return firestore;
    }),
  ],
})
export class AppModule {}

参考

https://stackoverflow.com/questions/59336720/cant-use-cypress-to-test-app-using-using-firestore-local-emulator

https://github.com/cypress-io/cypress/issues/2374

CauchyE は一緒に働いてくれる人を待ってます!

ブロックチェーンやデータサイエンスに興味のあるエンジニアを積極的に採用中です!
以下のページから応募お待ちしております。
https://cauchye.com/company/recruit

Discussion