如何实例化 AngularFireAuth 和 AngularFirestore 进行集成测试?

How to instantiate AngularFireAuth and AngularFirestore for integration testing?

我想编写一些 Karma/Jasmine 测试以确保我的 angular 后端服务按预期运行,但我在获取实例 AngularFireAuth 和我需要将其传递给我的各种服务的构造函数。

例如,我的 AuthService 的构造函数如下所示,我猜 Angular 知道如何以某种方式为依赖注入提供这些参数,因为我的应用程序是 运行 所期望的。但是为了测试,我真的希望有更细粒度的能力来调用我的服务的特定方法并确认我的可观察对象正在按预期响应。

constructor(
    private afAuth: AngularFireAuth,
    private afs: AngularFirestore,

  ) { ... }

但我想手动实例化它们,所以我可以在我的测试代码中手动将我的服务实例化为实例,而不必依赖测试平台、模拟和间谍,这是我在各种google 个关于该主题的结果。我想针对我的真实 Firestore 后端进行测试,而不是模拟,有点像在 RubyOnRails 中针对服务器上的开发数据库进行测试。

但是,我不知道如何为这些测试手动创建 AngularFireAuth 和 AngularFirestore 的实例,因此我可以将它们提供给我的服务的构造函数。这可能吗,还是我找错树了?

谢谢。

您可以尝试在 TestBed 配置中提供 angularfire 模块,如下所示

    // data.service.spec.ts
    import { TestBed } from '@angular/core/testing';

    import { DataService } from './data.service';
    import { environment } from 'src/environments/environment';
    import { AngularFireModule } from '@angular/fire';
    import { AngularFirestoreModule } from '@angular/fire/firestore';

    describe('DataService', () => {
      let service: DataService;

      beforeEach(() => {
        TestBed.configureTestingModule({
          imports: [
            AngularFireModule.initializeApp(environment.firebaseConfig),
            AngularFirestoreModule
          ],
          declarations: [ ],
          providers: [ ]
        });
        service = TestBed.inject(DataService);
      });

      it('should be created', () => {
        expect(service).toBeTruthy();
      });


      it('query should return data', (done) => {
        service.getQuery().subscribe((data) => {
          console.log('test data');
          console.log(data);

          expect(data).toEqual({text: 'test'});
          done();
        });
      });
    });

    // data.service.ts
    import { Injectable } from '@angular/core';
    import { AngularFirestore } from '@angular/fire/firestore';
    import { map, filter } from 'rxjs/operators';

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

      public constructor(private firestore: AngularFirestore) { }

      getQuery() {
        return this.firestore
          .collection<LogData>('logs', ref => ref.where('text', '==', 'test'))
          .get()
          .pipe(
            filter(ref => !ref.empty),
            map(ref => ref.docs[0].data() as LogData)
          );
      }
    }

    interface LogData {
      text: string;
    }

已更新

AngularFireAuth 示例


    // login.service.spec.ts

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

    import { environment } from 'src/environments/environment';
    import { LoginService } from './login.service';
    import { AngularFireModule } from '@angular/fire';
    import { AngularFireAuthModule } from '@angular/fire/auth';

    describe('LoginService', () => {
      let service: LoginService;

      beforeEach(() => {
        TestBed.configureTestingModule({
          imports: [
            AngularFireModule.initializeApp(environment.firebaseConfig),
            AngularFireAuthModule
          ],
          declarations: [ ],
          providers: [ ]
        });
        service = TestBed.inject(LoginService);
      });

      it('should be created', () => {
        expect(service).toBeTruthy();
      });

      it('signin should return user object', async () => {
        const loginData = {
          email: 'test@user.com',
          password: 'test user password'
        };
        const actual = await service.signin(loginData);
        expect(actual.uid).toEqual('S8HuZr2lbTOwUs1bWPNCuEDfpXe2');
      });
    });

    // login.service.ts
    import { Injectable } from '@angular/core';
    import { AngularFireAuth } from '@angular/fire/auth';

    export interface LoginData {
      email: string;
      password: string;
    }

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

      constructor(private auth: AngularFireAuth) {}

      async signin(loginData: LoginData) {
        const {email, password} = loginData;

        const responseOk = await this.auth
          .signInWithEmailAndPassword(email, password);

        return responseOk.user;
      }
    }

一个技术问题和一个哲学问题...很好

可能吗?

是的。您可以像使用任何其他关键字一样使用 new 关键字 class:

const auth      = new AngularFireAuth(...);
const firestore = new AngularFirestore(...);

您必须自己实例化 NgZonePLATFORM_ID 等依赖项。

AngularFire 是开源的。查看 classes 以了解您需要传递给它们的构造函数的内容:

AngularFireAuth | AngularFirestore

我是不是找错树了?

感觉像。三个原因:

  1. 如果你坚持手动实例化,你可能有你的理由。话虽这么说,手动实例化依赖项总是一件令人头疼的事情。这就是注射器的用途。考虑使用 TestBed 来初始化 Firebase,就像您在应用程序中所做的那样,然后使用 TestBed.inject(...) 来获取您需要的实例。您甚至可以创建一个每次都会为您提取它们的设置函数:

    
    // ==== Your setup file ====
    function setup()
    {
        TestBed.configureTestingModule({
            imports: [
                AngularFireModule.initializeApp(YOUR_FIREBASE_CONFIG),
                AngularFirestoreModule,
                // YOUR MODULE HERE
            ]
        });
    
        const fireAuth  = TestBed.inject(AngularFireAuth);
        const firestore = TestBed.inject(AngularFirestore);
        const auth      = TestBed.inject(AuthService);
    
        return { fireAuth, firestore, auth };
    }
    
    // ==== Your test file(s) ====
    let fireAuth;
    let firestore;
    let auth;
    
    beforeEach(() =>
    {
        ({ fireAuth, firestore, auth } = setup());
    });
    
  2. 您提到您不需要模拟,并且您希望确保您的angular 后端服务按预期运行。您要测试的到底是什么?
    单元测试是一种隔离功能的尝试。确保您正在测试 您的代码 是如何工作的,而不是将自己困在 "testing someone else's work" 范式中(即测试 Firebase 是否正在完成它的工作)。

  3. 确认我的 Observable 正在按预期响应
    那太棒了。现在考虑以下问题:Firebase 的服务器出现故障,就在您 运行 正在测试管道的那一刻。你的测试应该失败吗?当然不是。这就是间谍和嘲笑的目的:隔离和独立。

其他注意事项

test against a dev database on the server

Firebase 不提供开发服务器,但提供模拟器。您可以 运行 firestore 的本地实例并将您的测试重定向到它。查看测试套件:

Setting up a firestore emulator | Testing SDK