アルパカログ

Webエンジニア兼マネージャーがプログラミングやマネジメント、読んだ本のまとめを中心に書いてます。

AngularJS テストでAngularFire2のsnapshotChangesをスタブする方法(Firestore)

f:id:otoyo0122:20200906215703p:plain:w300

Angular.js + Firestoreという構成では、クライアントライブラリとしてAngularFire2を使うケースが多いと思います。

AngularFire2では、Firestoreからデータ(コレクション)を取得するにはvalueChanges()snapshotChanges()の2つの方法があります。

このうちvalueChanges()はドキュメントIDなどのメタデータを含まないデータそのものだけを返しますが、更新系の処理を行うにはsnapshotChanges()を使ってメタデータを含めてデータを取得する必要があります。

テストの際、メタデータを含まないデータのみを返すvalueChanges()をスタブするのは簡単です。

しかし、メタデータを含めたデータを返すsnapshotChanges()をスタブするのは少し面倒です。

そこでこのエントリでは、AngularJSのテストでAngularFire2のsnapshotChangesをスタブする方法を紹介します。

UserServiceクラスの例

よくあるUserServiceクラスの例を下記に挙げます。公式サンプル通りなので特に説明は不要でしょう。

src/app/user.service.ts

import { Injectable } from '@angular/core';
import { User } from './user';
import { AngularFirestore } from '@angular/fire/firestore';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class UserService {

  constructor(private afs: AngularFirestore) { }

  /**
   * 全てのUserを取得する
   */
  getUsers(): Observable<User[]> {
    return this.afs.collection('users').snapshotChanges().pipe(
      map(actions => actions.map(action => {
        const doc = action.payload.doc;
        const user = doc.data() as User;
        return { id: doc.id, ...user };
      }))
    );
  }
}

テストの例

次にsnapshotChanges()をスタブします。

結論から言うと、snapshotChanges()DocumentChangeAction<T>[]を型引数に持つObservableを返します。下記のように定義されています。

snapshotChanges(events?: DocumentChangeType[]): Observable<DocumentChangeAction<T>[]>

実際のスタブの例を見てみましょう。

DocumentChangeAction[]を作成しJasmine.createSpysnapshotChanges()の返り値に設定しているところに注目してください。

他の注意点としてはDocumentChangeActionDocumentChangeを、DocumentChangeDocumentSnapshotをインターフェースとして持つところです。

興味のある人はソースを読んでみてくださいね。

src/app/user.service.spec.ts

import { TestBed } from '@angular/core/testing';

import { AngularFirestore } from '@angular/fire/firestore';
import { DocumentChangeAction } from '@angular/fire/firestore/interfaces';
import { User } from './user';
import { UserService } from './user.service';
import { Observable, of } from 'rxjs';

const fixtureData = [
  {
    id: 'foo',
    email: 'foo@example.com',
    name: 'fooName',
  },
  {
    id: 'bar',
    email: 'bar@example.com',
    name: 'barName',
  },
  {
    id: 'baz',
    email: 'baz@example.com',
    name: 'bazName',
  },
];

const documentChangeActions: DocumentChangeAction<User>[] = fixtureData.map(data => {
  const user: User = data;
  const documentSnapshot = { id: user.id, data: (() => user) };
  const documentChange = { doc: documentSnapshot };
  return { payload: documentChange } as DocumentChangeAction<User>;
});

const collectionStub = {
  snapshotChanges: jasmine.createSpy('snapshotChanges').and.returnValue(of(documentChangeActions))
};
const angularFirestoreStub = {
  collection: jasmine.createSpy('collection').and.returnValue(collectionStub)
};

describe('UserService', () => {
  beforeEach(() => TestBed.configureTestingModule({
    providers: [
      UserService,
      { provide: AngularFirestore, useValue: angularFirestoreStub }
    ]
  }));

  describe('getUsers()', () => {
    it('returns users as Observable', () => {
      const service: UserService = TestBed.get(UserService);
      service.getUsers().subscribe(users => expect(users).toEqual(fixtureData as User[]));
    });
  });
});

以上です。

このエントリでは、AngularJSのテストでAngularFire2のsnapshotChangesをスタブする方法を紹介しました。

参考になった方は、ぜひ「はてブ」やSNSでシェアしていただけると嬉しいです。