5

我正在尝试测试使用 a 的服务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); 
      });
    }));
4

2 回答 2

7

当您测试可观察序列时,请务必记住发出的值稍后(异步)出现(JavaScript 是单线程的,但您可以将其视为单独的线程)。因此,尝试使用模拟数据测试它们并没有明显的错误。

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);
  })));

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

于 2018-05-30T14:59:29.983 回答
6

我不确定你为什么首先使用 a 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);
}

类似的东西。

于 2017-08-03T22:02:13.817 回答