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,那么 list
和 key
可能是我可以在 运行 测试之前设置的 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.
来模拟它们
我有一个测试文件正在测试 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,那么 list
和 key
可能是我可以在 运行 测试之前设置的 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.
来模拟它们