如何测试BehaviourSubject的下一个功能?

时间:2017-08-03 21:27:27

标签: angular jasmine rxjs behaviorsubject

我正在尝试测试使用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); 
      });
    }));

2 个答案:

答案 0 :(得分:2)

我不确定您为什么首先使用BehaviorSubject,但是因为您的getProducts() API正在使用可观察的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);
  })));

在这种情况下写一个间谍似乎并没有多大意义,因为你所做的就是测试测试代码。如果您正在尝试测试服务本身,您可以存根返回服务器的方法,或者您可以编写一个模拟组件(这是我通常做的)来做这个(我通常会做我的模拟组件和实际组件共享一个公共基类。)