如何测试与 Firebase 交互的服务?

How to test a service that interacts with Firebase?

从 Firebase 获取数据的惯例似乎是创建一个在内部使用 AngularFire 的服务 class。然后,在您应用程序的组件中,您可以注入和使用该服务。这使您可以在对组件进行单元测试时轻松模拟服务。

但是,我仍然不清楚如何着手测试实际服务本身。

举个例子,假设我在 Firebase 中有一个 todo list 节点,我有一个简单的服务可以获取所有 todos,就像这样:

import { Injectable } from '@angular/core';
import { AngularFire } from 'angularfire2';

@Injectable()
export class TodoService {
  constructor(private af: AngularFire) { }

  getAll(): FirebaseListObservable<any[]> {
    return this.af.database.list('todos');
  }
}

您将如何测试此服务是否符合您的预期?

这个问题主要是基于意见,因此,我只是把我的意见放在这里。

我会在 Firebase 中创建一个与我的开发数据库模型完全相同的测试节点。因此,在编写单元测试时,我会插入一些数据并再次检索它们以与我之前插入的数据相匹配。

基本上,Firebase 将数据存储在 Json 结构中。所以我会写一些测试 Json todo 条目。我不会嘲笑 Firebase 参考对象或连接,因为这基本上是第三方软件。我会在 运行 单元测试时与 Firebase 建立真正的联系,插入一些数据并检索它们并与我之前插入的数据匹配。那将是我的方法。

更新

是的,您对持续集成有一些合理的担忧。老实说,我以前从未在团队和 CI 服务器上做过这样的事情。我不能保证流畅的体验。

在寻找更好的解决方案时,我发现 MockFirebase 可能符合您的目的。你可以看看。

我想参考 this answer,他建议将 Firebase 调用保存在库中。虽然,这不考虑 CI 服务器。

Reaz 在他的回答中推荐了 MockFirebase。但是,MockFirebase 不适用于 Firebase 3。有一个名为 firebase-mock 的库,它似乎是由与 MockFirebase 相同的一些人开发的。这增强了 MF,添加了 Firebase 3 支持,但是它 doesn't appear to play nicely with WebPack yet.

这是我最后做的事情:

我想到 FirebaseListObservable 继承自 RXJS 的 Observable。所以,你可以模拟 AngularFire 对 list 的调用,并让它 return 你自己的 Observable。

一些示例代码展示了这一点:

todos.service.spec.ts

let fixtureTodos = [
  { 'text': 'Get milk' },
  { 'text': 'Take out the trash' },
  { 'text': 'Get gas for the car' },
  { 'text': 'Pay parking ticket' },
  { 'text': 'Pick up dry cleaning' },
];
let angularFireDatabaseStub = { list: () => {} };
let mockTodos$ = Observable.of(fixtureTodos);

describe('TodosService', () => {
  beforeEach(() => {
    spyOn(angularFireDatabaseStub, 'list').and.returnValue(mockTodos$);

    TestBed.configureTestingModule({
      providers: [
        TodosService,
        { provide: AngularFireDatabase, useValue: angularFireDatabaseStub },
      ]
    });
  });

  it('#getAll', inject([TodosService], (service: TodosService) => {
    let todos$ = service.getAll();
    todos$.subscribe(todos => {
      expect(todos[0].text).toEqual(fixtureTodos[0].text);
      expect(todos[0]).toEqual(jasmine.any(Todo));
    });
  }));
});

todos.service.ts

@Injectable()
export class TodosService {
  constructor(public db: AngularFireDatabase) { }

  getAll(): Observable<Todo[]> {
    return this.db.list('todos').map(arr => {
      return arr.map(t => new Todo(t.text));
    });
  }
}

todo.model.ts

export class Todo {
  constructor(public text: string) {}
}