我正在尝试测试使用BehaviourSubject
的服务,但我不确定如何。这是我的服务:
export class ProductService {
private changes$: BehaviorSubject<User[]> = new BehaviorSubject([]);
get products() { return this.changes$.asObservable() as Observable<any>; }
getProducts() {
this.http.get(...)
.subscribe((products) => {
this.changes$.next(products);
}
}
filterById(id: number) {
let products = this.products$.getValue().filter(...);
// Maybe I will have some more filter logic here.
// I want to test that the filter logic is returning the correct product
this.changes$.next(products);
}
...
}
这是我的测试。我希望能够测试我的服务的过滤器逻辑。但由于我在嘲笑数据,this.product$.getValue()
将为空,过滤器将无法运行。
it('should filter the products and return the correct product)',
inject([ProductService], (service: ProductService) => {
const usersSpy = spyOnProperty(service, 'products', 'get').and.returnValue(Observable.of(mockGetResponse));
service.products.subscribe((res) => {
// How do I continue here?
// the `this.products$.getValue()`
// will not contain any items,
// because I am mocking the data.
service.findByUserId(1);
});
}));
答案 0 :(得分:2)
我不确定您为什么首先使用BehaviorSubject
,但是因为您的getProducts()
API正在使用可观察的API,所以它是最好立即公开它,以便API的消费者可以决定如何处理发出的值/失败。
通过这样做,过滤也变得更容易,而且它更容易测试。
所以我基本上建议:
getProducts(): Observable<Product[]> {
return this.http.get(...);
}
然后在您的过滤器方法中,您可以重用该API
filterById(id: string): Observable<Product> {
return this.getProducts()
.filter(products => products.find(product => id === product.id));
}
然后你基本上将它用作:
productService.filterById(id).subscribe(product => ...);
现在,在测试方面,您可以轻松模拟getProducts()
提供的响应并测试您的过滤方法:
spyOn(productService, 'getProducts').and.returnValue(Observable.of([
{ id: '0', ... },
{ id: '1', ...}
]);
productService.filterById('0').subscribe(product => {
...assertions
});
当然,每次按ID过滤时都要联系到服务器可能不是您想要的。在这种情况下,教你的服务缓存http响应并立即返回它是好的:
getProducts(): Observable<Product[]> {
if (this.products) {
return Observable.of(products);
}
return this.http.get(...)
.do(products => this.products = products);
}
这些方面的东西。
答案 1 :(得分:2)
当您测试一个可观察的序列时,请务必记住,所发出的值与它们产生的线程不同。因此,尝试使用模拟数据测试它们并没有明显的错误。
BehaviorSubject
是一个极端情况,因为它被编程为在调用subscribe()
时发出某种类型的默认值。因此,您可能希望第一次跳过此值。
it('should emit filtered products', async(inject([ProductService], (service: MockProductService) => {
let testId = 'testId'; // you need to define this as appropriate
let testProduct; // you need to define this to be something that will be returned when the filter is set
// subscribe, but skip the first as this is a BehaviorSubject and emits a default first value.
service.filteredProducts$.skip(1).subscribe((o) => expect(o).toBe(testProduct));
service.findByUserId(testId);
})));
在这种情况下写一个间谍似乎并没有多大意义,因为你所做的就是测试测试代码。如果您正在尝试测试服务本身,您可以存根返回服务器的方法,或者您可以编写一个模拟组件(这是我通常做的)来做这个(我通常会做我的模拟组件和实际组件共享一个公共基类。)