单元测试时发生错误-TypeError:无法读取未定义的属性“ cityWeather”

时间:2019-05-28 13:50:37

标签: javascript angular typescript unit-testing

在Component.ts中,

this.dataService.currentCity.subscribe(info => {
  this.cities = info;
});

this.dataService.currentSelectedCity.subscribe(index => {
  this.selectedCityIndex = index;
});

this.selectedCityInfo = this.cities[this.selectedCityIndex];
this.selectedCityWeatherList = this.selectedCityInfo.cityWeather.list;

我正在尝试在ngOnInit()中测试这段代码。但是我不确定如何测试此代码。在这里,我的另一个组件正在使用行为主题向我发送对象数组,以及所选项目的索引,我正在订阅该组件的ngOnInit()。并尝试访问所选对象中的cityWeather属性。

在Spec.ts中,我尝试过类似的方法,但我肯定这不是测试此方法的正确方法。

it("should handle ngOnInit", async(() => {
        let response;
        const dataService = fixture.debugElement.injector.get(DataService);  
        spyOn(dataService, 'currentCity').and.returnValue(of(response));
        component.ngOnInit();
        expect(component.cities).toEqual(response);
    }));

当我运行应用程序时,我得到了输出,但是当我测试ngoninit函数时,却没有得到订阅的响应。我收到以下错误。 Failed: Cannot read property 'cityWeather' of undefined TypeError: Cannot read property 'cityWeather' of undefined

在data.service.ts

private citySubject = new BehaviorSubject<any>('');
currentCity = this.citySubject.asObservable();
private selectedCity = new BehaviorSubject<any>('');
currentSelectedCity= this.selectedCitySubject.asObservable();

sendCityWeatherInfo(info,index) {
  this.citySubject.next(info);
  this.selectedCitySubject.next(index);
}

我在这样的组件中调用此函数(传递对象和索引数组)

this.dataService.sendCityWeatherInfo(this.cities, index);

2 个答案:

答案 0 :(得分:0)

该错误不在您的测试中,而是在以下行中:

this.selectedCityInfo = this.cities[this.selectedCityIndex];

this.citiesthis.selectedCityIndex尚未定义-您订阅了dataService,它将对其进行更新,但该服务未运行。另外,当服务运行时,this.selectedCityInfo不会被更新。

相反,每当您收到新数据时都更新这些值。一个简单的解决方案是:

  this.dataService.currentCity.subscribe(info => {
    this.cities = info;
    this.updateSelectedCity();
  });

  this.dataService.currentSelectedCity.subscribe(index => {
    this.selectedCityIndex = index;
    this.updateSelectedCity();
  });
}

...

updateSelectedCity() {
  this.selectedCityInfo = this.cities[this.selectedCityIndex];
  this.selectedCityWeatherList = this.selectedCityInfo.cityWeather.list;
}

对于测试,this.selectedCityInfothis.selectedCityWeatherList未被测试。我对测试库不熟悉,但是您可能想要类似expect(component.selectedCityInfo).toEqual(...)的东西和this.selectedCityWeatherList一样的东西。

答案 1 :(得分:0)

由于您正在处理本质上是异步的可观察流, this.citiesthis.selectedCityIndex可以以未知顺序或在未知时间进行更新。

因此,下面的同步代码会降低处理未定义值的机会。

this.selectedCityInfo = this.cities[this.selectedCityIndex];
this.selectedCityWeatherList = this.selectedCityInfo.cityWeather.list;

例如,当您尝试分配this.selectedCityInfo时,this.citiesthis.selectedCityIndex可能是未定义的,分配将失败。 而且,在您的服务中,您会使用不同数据类型的空字符串来初始化两个行为主体。

这种设计会引发各种问题和意外行为。 我建议避免以这种方式设计数据流。

相反,我建议通过使用combineLatest函数将两个可观察值组合在一个订阅中,或者通过以下方式重构位:

由于在服务updateSelectedCity方法中您同时从两个可观察对象中发出,也许(如果适合您的应用程序)在一个可观察对象中同时发出两个值会更容易吗?然后,我们知道两个值肯定会到达,并且可以在订阅回调函数中进行this.selectedCityInfothis.selectedCityWeatherList分配。

在使用中:

/* you can keep private property and recast it to Observable,
but you can also subscribe to Subjects and BehaviorSubjects directly
since they extend Observable class.
So that's what I'm doing here */

cityWeatherInfo$ =  = new BehaviorSubject<any>({ info: null, index: null });

sendCityWeatherInfo(info,index) {
  this.cityWeatherInfo$.next({ info, index });

}

在组件中:

this.dataService.cityWeatherInfo$
  .subscribe(({ info, index }) => {
    if (info !== null && index !== null) {
      this.cities = info;
      this.selectedCityIndex = index;
      this.selectedCityInfo = this.cities[this.selectedCityIndex];
      this.selectedCityWeatherList = this.selectedCityInfo.cityWeather.list;
    }    
  });

P.S。我不知道您应用程序的全部要求,但是也许您可以在服务中使用简单的Subject而不是BehaviorSubject。这样,它就不会发出初始空值,也不需要进行if (info !== null && index !== null)检查。