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

AngularFireDatabase,Jest和单元测试,如何创建可重用的类存根?,angular,unit-testing,jestjs,angularfire,Angular,Unit Testing,Jestjs,Angularfire,我有一个测试文件,它正在测试从AngularFireDatabase返回数据的服务: 从'@angular/core/testing'导入{TestBed,async}; 从“./product.service”导入{ProductService}; 从'@angular/fire/database'导入{AngularFireDatabase}; 从“../../../../mocks/products.mock”导入{productsMock}; 从“共享”导入{Product}; 从“rx

我有一个测试文件,它正在测试从AngularFireDatabase返回数据的服务:

从'@angular/core/testing'导入{TestBed,async};
从“./product.service”导入{ProductService};
从'@angular/fire/database'导入{AngularFireDatabase};
从“../../../../mocks/products.mock”导入{productsMock};
从“共享”导入{Product};
从“rxjs”导入{Observable};
从'src/app/test/helpers/AngularFireDatabase/getSnapShotChanges'导入{getSnapShotChanges};
出租清单:产品[];
let键:字符串=“”;
常量afDatabaseStub={
db:jest.fn().mockReturnThis(),
列表:jest.fn(()=>({
snapshotChanges:jest.fn().mockReturnValue(getSnapShotChanges(list,true)),
valueChanges:jest.fn(
()=>newobservable(sub=>sub.next(Object.values(list)))
)
})),
对象:jest.fn(()=>({
valueChanges:jest.fn(()=>newobservable(sub=>sub.next({id:key})))
}))
};
描述('ProductService',()=>{
让服务:产品服务;
在每个之前(()=>{
TestBed.configureTestingModule({
提供程序:[{提供:AngularFireDatabase,useValue:afDatabaseStub}]
});
service=TestBed.inject(ProductService);
});
它('应该创建',()=>{
expect(service.toBeTruthy();
});
描述('getAllProducts',()=>{
它('应该能够返回所有产品'),异步(()=>{
列表=productsMock;
service.getAllProducts().subscribe((产品:Product[])=>{
期望(产品?长度)。toEqual(10);
});
}));
});
它('应该能够使用firebase id返回单个产品',异步(()=>{
key='-MA_EHxxDCT4DIE4y3tW'
const response$=service.getProductById(键);
响应$.subscribe((giveawayProduct:giveawayProduct)=>{
expect(giveawayProduct).toBeDefined();
expect(giveawayProduct.id).toEqual(key);
});
}));
});
我面临的问题是,我现在想测试另一个也使用AngularFireDatabase的服务

那么,我如何使这个存根更通用,并将其放入一个共享的帮助文件中,以便在不同的规范中使用呢

例如,我知道您可以使用
useClass
而不是
useValue

提供程序:[{provide:AngularFireDatabase,useClass:afDatabaseStub}]

如果它是一个类,那么
list
key
可以是我可以在运行测试之前设置的类属性

但当我尝试时,会出现如下错误:

interface IProductService {
    getProducts(): Observable<product>;
    getProduct(id: string): Observable<product>;
    //And all your other methods.
}
db.list.object
不是函数


db.list(…).snapshotchanges
不是一个函数

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

interface IProductService {
    getProducts(): Observable<product>;
    getProduct(id: string): Observable<product>;
    //And all your other methods.
}
接口IPProductService{
getProducts():可观察的;
getProduct(id:string):可观察;
//还有你所有的其他方法。
}
现在创建实现接口的产品服务:

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

    getProducts(): Observable<product>{
        return this.angularFire....
    }
    //And all your other methods.
}
ProductService实现IPProductService{
构造函数(angularFire:angularFire){}
getProducts():可观察{
把这个还给我,angularFire。。。。
}
//还有你所有的其他方法。
}
现在,对于您的测试,您可以创建一个veery simple mock实例:

MockProductService implements IProductService {
    constructor(){}

    getProducts(): Observable<product>{
        return of([new Product("One"), new Product("Two")])
    }
    //And all your other methods.
}
MockProductService实现IPProductService{
构造函数(){}
getProducts():可观察{
([新产品(“一”)、新产品(“两”)]的退货
}
//还有你所有的其他方法。
}

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

您需要的一切都已经具备。接下来需要做的事情是将模拟对象提取到一些
.ts
文件中并导出它

我通常在其中创建
testhelpers
文件夹和
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};
}
然后您只需调用
getAngularFireMock()
inside
providers
array即可


然后,如果您需要更改其他测试的值,您只需使用Jest的API模拟它们。

谢谢,但这是行不通的。如果
list
是一组测试的模拟产品数据,而客户是另一组测试的模拟产品数据,您将如何更改列表中包含的内容?在助手中,“list”是一个本地定义的变量,因此始终包含您在其中声明的任何数据。它不能从一个测试更改为另一个测试。您正在创建模拟。然后,您需要使用测试床功能注入您的服务。之后,使用JESTAPI可以重写spies返回值。我可以给你看一个使用茉莉花的例子。它离开玩笑不远,但展示了用例。间谍的想法是他们是动态的,你可以随时改变他们。对不起,我想你误解了。你的答案就是把我的代码放到一个可以导入的外部函数中<代码>对象。值(列表)不会返回任何数据,因为代码中未定义列表。在我的代码列表中,类型为
Product[]
,数据来自导入的
productsMock
文件。那么,当列表需要为
Customer[]
类型时会发生什么情况呢?您的代码将如何向列表提供模拟的客户数据,以便存根可以返回它?这就是我问题的全部要点。如你所愿。阅读有关如何通过模拟更改返回值的文档。您所需要的只是为您需要的任何方法/属性定义mock,然后在某些地方您可以指定您期望从它们返回的内容。谢谢,所以通过使用接口,我们完全不需要创建angularFire存根,对吗?我们只是实现了一个直接返回数据的模拟版本的服务?如果我理解正确,那似乎太容易了!就这么简单