我已经为此功能编写了单元测试:
getCarsAndSetup(){
this.getCars();
this.getFactoryInfo();
}
这是getCars()函数:
getCars() {
const subscription = this.carDetailsService.getAll().subscribe((carDetails) => {
this.carInfoService.setCars(carDetails);
subscription.unsubscribe(); <-------------- Here the
subscription is undefined
when running the test,
however when running
the app, the subscription
is defined and
everything is fine
});
}
这是单元测试:
fdescribe('getCarsAndSetup', () => {
it('should get cars and set them up', () => {
component.getFactoriesAndUnsubscribe();
spyOn(component, "getCars");
spyOn(component, "getFactoryInfo");
expect(component.getCars).toHaveBeenCalled();
expect(component.getFactoryInfo).toHaveBeenCalled();
});
});
我正在为carDetailsService使用模拟。这是carDetailsService模拟中的getAll()方法:
getAll(): Observable<CarModel[]> {
return Observable.create((observer:any) => {
observer.next([]);
});
}
这与REAL carDetailsService中的方法相同:
getAll(): Observable<CarModel[]> {
return this.http.get<CarModel[]>(this.carUrl);
}
问题在于,当我运行应用程序本身时,定义了getCars()方法中的订阅,因此可以取消订阅等,一切都很好。
但是,当我运行测试时,该测试失败,因为由于某种原因,当我尝试取消订阅时,getCars()函数中的订阅未定义。
仅在运行测试时未定义订阅的原因可能是什么?可能与我模仿carDetailsService的getAll()函数的方式有关吗?
答案 0 :(得分:1)
这里的问题是您依赖源Observable的同步/异步行为。
在您的真实应用中,您的this.carDetailsService.getAll()
是一个真正的远程调用(异步),因此其订阅已分配给subscription
,并且一切正常。然而,在您的测试中,相同的调用可能是模拟的,因此是同步的,因此在您要调用subscription.unsubscribe()
时,它仍为undefined
(subscribe
方法仍在执行且未返回任何预订)。
最简单的方法是使用subscribe
关键字将箭头函数传递给function
。 RxJS将订户处理程序中的this
绑定到其内部的Subscription对象(我知道这是一个棘手的方法,但打算以此方式使用)。
const that = this;
this.carDetailsService.getAll().subscribe(function(carDetails) { // note the `function` keyword
that.carInfoService.setCars(carDetails);
this.unsubscribe();
});
另一种方法可能是将takeUntil
与主题一起使用,并在subscribe
内完成它。
此行为将来可能会更改:https://github.com/ReactiveX/rxjs/issues/3983
在不同用例中的相同问题:RxJs: Calculating observable array length in component
答案 1 :(得分:0)
尽管马丁的回答确实消除了错误,但它帮助我发现了这里的实际问题,这很可笑。我已经在实际的函数调用之后设置了间谍:
fdescribe('getCarsAndSetup', () => {
it('should get cars and set them up', () => {
component.getFactoriesAndUnsubscribe();
spyOn(component, "getCars");
spyOn(component, "getFactoryInfo");
expect(component.getCars).toHaveBeenCalled();
expect(component.getFactoryInfo).toHaveBeenCalled();
});
});
当必须在实际函数调用之前定义间谍时:
fdescribe('getCarsAndSetup', () => {
it('should get cars and set them up', () => {
spyOn(component, "getCars");
spyOn(component, "getFactoryInfo");
component.getFactoriesAndUnsubscribe();
expect(component.getCars).toHaveBeenCalled();
expect(component.getFactoryInfo).toHaveBeenCalled();
});
});
我很不高兴马丁花了很多时间在这个答案上,并阅读了我发表的详细说明,结果整个问题只是一个小小的疏忽。但这就是它。