AngularFireDatabase、Jest 和单元测试,如何创建可重用的 class 存根?

AngularFireDatabase, Jest and Unit Testing, how to create a reusable class stub?

我有一个测试文件正在测试 returns 来自 AngularFireDatabase 的数据的服务:

import {TestBed, async} from '@angular/core/testing';
import {ProductService} from './product.service';
import {AngularFireDatabase} from '@angular/fire/database';
import {productsMock} from '../../../../mocks/products.mock';
import {Product} from 'shared';
import {Observable} from 'rxjs';
import {getSnapShotChanges} from 'src/app/test/helpers/AngularFireDatabase/getSnapshotChanges';

let list: Product[];
let key: string = '';

const afDatabaseStub = {
  db: jest.fn().mockReturnThis(),
  list: jest.fn(() => ({
    snapshotChanges: jest.fn().mockReturnValue(getSnapShotChanges(list, true)),
    valueChanges: jest.fn(
      () => new Observable(sub => sub.next(Object.values(list)))
    )
  })),
  object: jest.fn(() => ({
    valueChanges: jest.fn(() => new Observable(sub => sub.next({id: key})))
  }))
};

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

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [{provide: AngularFireDatabase, useValue: afDatabaseStub}]
    });
    service = TestBed.inject(ProductService);
  });

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

  describe('getAllProducts', () => {
    it('should be able to return all products', async(() => {
      list = productsMock;

      service.getAllProducts().subscribe((products: Product[]) => {
        expect(products?.length).toEqual(10);
      });
    }));
  });

  it('should be able to return a single product using the firebase id', async(() => {
    key = '-MA_EHxxDCT4DIE4y3tW'
    const response$ = service.getProductById(key);
    response$.subscribe((giveawayProduct: GiveawayProduct) => {
      expect(giveawayProduct).toBeDefined();
      expect(giveawayProduct.id).toEqual(key);
    });
  }));

});

我面临的问题是我现在想测试另一个也使用 AngularFireDatabase 的服务。

那么我怎样才能使这个存根更通用,并将其放入我可以在不同规范中使用的共享帮助文件?

例如,我知道你可以做 useClass 而不是 useValue:

providers: [{provide: AngularFireDatabase, useClass: afDatabaseStub}]

如果它是 class,那么 listkey 可能是我可以在 运行 测试之前设置的 class 属性。

但是当我尝试这样做时,出现如下错误:

db.list.object 不是函数

db.list(...).snapshotchanges 不是函数

我认为最好的方法是在 AngularFire 和您的组件之间创建一个抽象层。像这样:

interface IProductService {
    getProducts(): Observable<product>;
    getProduct(id: string): Observable<product>;
    //And all your other methods.
}

现在创建实现接口的产品服务:

ProductService implements IProductService {
    constructor(angularFire: AngularFire){}

    getProducts(): Observable<product>{
        return this.angularFire....
    }
    //And all your other methods.
}

现在,对于您的测试,您可以创建一个非常简单的模拟实例:

MockProductService implements IProductService {
    constructor(){}

    getProducts(): Observable<product>{
        return of([new Product("One"), new Product("Two")])
    }
    //And all your other methods.
}

您可以根据需要使模拟变得简单或复杂。

你所需要的你已经拥有了。接下来你需要做的是将模拟对象提取到某个 .ts 文件中并将其导出。

我通常会在其中创建 test-helpers 文件夹和 providers.ts。我在那里声明了一些函数,这些函数为我提供了最常见的提供程序模拟。

export function getAngularFireMock() {
  const afDatabaseStub = {
    db: jest.fn().mockReturnThis(),
    list: jest.fn(() => ({
      snapshotChanges: jest.fn().mockReturnValue(getSnapShotChanges(list, true)),
      valueChanges: jest.fn(
        () => new Observable(sub => sub.next(Object.values(list)))
      )
    })),
    object: jest.fn(() => ({
      valueChanges: jest.fn(() => new Observable(sub => sub.next({id: key})))
    }))
  };

  return {provide: AngularFireDatabase, useValue: afDatabaseStub};
}

然后您只需在 providers 数组中调用 getAngularFireMock() 即可。

然后,如果您需要更改其他测试的值,您只需使用 Jest 的 API.

来模拟它们