[英]TypeError: Cannot read property 'modelReplicationStartDate' of undefined
使用 Jasmine/Karma 運行 Angular 單元測試時,出現以下錯誤:
TypeError: Cannot read property 'modelReplicationStartDate' of undefined
error properties: Object({ longStack: 'TypeError: Cannot read property 'modelReplicationStartDate' of undefined
at http://localhost:9876/_karma_webpack_/main.js:11595:146
at http://localhost:9876/_karma_webpack_/vendor.js:145322:30
at memoized (http://localhost:9876/_karma_webpack_/vendor.js:145203:39)
at defaultStateFn (http://localhost:9876/_karma_webpack_/vendor.js:145226:43)
at http://localhost:9876/_karma_webpack_/vendor.js:145325:36
at memoized (http://localhost:9876/_karma_webpack_/vendor.js:145203:39)
at MapSubscriber.project (http://localhost:9876/_karma_webpack_/vendor.js:145150:107)
at MapSubscriber._next (http://localhost:9876/_karma_webpack_/vendor.js:133260:35)
at MapSubscriber.next (http://localhost:9876/_karma_webpack_/vendor.js:39429:18)
at MockState._subscribe (http://localhost:9876/_karma_webpack_/vendor.js:31954:24)
at ____________________Elapsed_40_ms__At__Fri_Jun_11_2021_13_51_42_GMT_0400__Eastern_Daylight_Time_ (http://localhost)
...
TypeError: Cannot read property 'modelReplicationStartDate' of undefined
at http://localhost:9876/_karma_webpack_/main.js:11595:146
at http://localhost:9876/_karma_webpack_/vendor.js:145322:30
at memoized (http://localhost:9876/_karma_webpack_/vendor.js:145203:39)
at defaultStateFn (http://localhost:9876/_karma_webpack_/vendor.js:145226:43)
at http://localhost:9876/_karma_webpack_/vendor.js:145325:36
at memoized (http://localhost:9876/_karma_webpack_/vendor.js:145203:39)
at MapSubscriber.project (http://localhost:9876/_karma_webpack_/vendor.js:145150:107)
at MapSubscriber._next (http://localhost:9876/_karma_webpack_/vendor.js:133260:35)
at MapSubscriber.next (http://localhost:9876/_karma_webpack_/vendor.js:39429:18)
at MockState._subscribe (http://localhost:9876/_karma_webpack_/vendor.js:31954:24)
at <Jasmine>
at Object.onScheduleTask (http://localhost:9876/_karma_webpack_/vendor.js:89765:26)
at ZoneDelegate.scheduleTask (http://localhost:9876/_karma_webpack_/polyfills.js:3727:55)
at Object.onScheduleTask (http://localhost:9876/_karma_webpack_/polyfills.js:3621:69)
at ZoneDelegate.scheduleTask (http://localhost:9876/_karma_webpack_/polyfills.js:3727:55)
at Zone.scheduleTask (http://localhost:9876/_karma_webpack_/polyfills.js:3559:47)
at Zone.scheduleMacroTask (http://localhost:9876/_karma_webpack_/polyfills.js:3582:29)
at scheduleMacroTaskWithCurrentZone (http://localhost:9876/_karma_webpack_/polyfills.js:4483:29)
at http://localhost:9876/_karma_webpack_/polyfills.js:5935:34
我的測試套件很簡單:
describe('ReplicateFilterComponent', () => {
let component: ReplicateFilterComponent;
let fixture: ComponentFixture<ReplicateFilterComponent>;
let store: MockStore;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
FormsModule,
RouterTestingModule.withRoutes([]),
ReactiveFormsModule,
BsDatepickerModule.forRoot(),
HttpClientTestingModule,
StoreModule.forRoot({
upload: uploadReducer,
[managementServiceFeatureKey]: managementServiceReducer,
assets: modelReducer
})
],
declarations: [
ReplicateFilterComponent,
TranslatePipe
],
providers: [
DatePipe,
DecimalPipe,
TranslatePipe,
TextSubstitutePipe,
RuntimeConfig,
provideMockStore({
initialState
})
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ReplicateFilterComponent);
component = fixture.componentInstance;
TestBed.inject(MockStore);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
如您所見,我嘗試使用初始 state 創建一個模擬商店。 state 代碼的相關部分:
export interface IReplicateState {
sortReplicateBy: SortReplicateOptions;
modelName: string;
modelReplicationStartDate: ReplicationStartDateValue;
modelReplicationStartDate1: string; // 'YYYY-MM-DD'
modelReplicationStartDate2: string; // ''YYYY-MM-DD'
filterTab: FilterTabValue;
}
...
export const initialState: IReplicateState = {
sortReplicateBy: 'nameaz',
modelName: null,
modelReplicationStartDate: 'Between',
modelReplicationStartDate1: null,
modelReplicationStartDate2: null,
filterTab: 'all',
};
調試器顯示“狀態”未定義:
const getModelReplicationStartDate = Object(_ngrx_store__WEBPACK_IMPORTED_MODULE_0__["createSelector"])(getReplicateFeatureState, state => state.modelReplicationStartDate);
我遵循了Uncaught TypeError: Cannot read property 'coSearchCriteria' of undefined throw - Angular Karma/Jasmine中的建議和https://ngrx.io/guide/store/testing中的文檔
請告訴我主題中錯誤的原因和修復方法。 謝謝。
編輯#1-組件代碼
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { select, Store } from '@ngrx/store';
import moment from 'moment';
import { BsDatepickerConfig } from 'ngx-bootstrap/datepicker';
import { debounceTime } from 'rxjs/operators';
import { replicateModel, replicateActions, replicateSelectors } from 'src/app/replicate/state';
import { TranslateService } from 'src/app/core/services/translate.service';
import { BaseContainerComponent } from 'src/app/shared/components/base-container.component';
import { Filter } from 'src/app/shared/types/filter.type';
import { IReplication } from 'src/app/shared/interfaces/replication.interface';
import { FilterTabValue, ReplicationStartDateValue } from '../../state/replicate.state';
@Component({
selector: 'app-replicate-filter',
templateUrl: './replicate-filter.component.html',
styleUrls: ['./replicate-filter.component.scss']
})
export class ReplicateFilterComponent extends BaseContainerComponent implements OnInit {
static readonly DEBOUNCE_TIME = 500;
private readonly filterByReplicationStartBetweenText = 'models_list_filter_key_start_date_between';
private readonly filterByReplicationStartBeforeText = 'models_list_filter_key_start_date_before';
private readonly filterByReplicationStartAfterText = 'models_list_filter_key_start_date_after';
@Output() filters: EventEmitter<Array<Filter<IReplication>>> = new EventEmitter<Array<Filter<IReplication>>>();
replicationKeyStartList: any[];
filterTab: FilterTabValue;
modelFilterForm: FormGroup;
filterReplicationNameControl = new FormControl('');
filterReplicationStatusControl = new FormControl('All');
filterReplicationStartDateControl = new FormControl('Between');
filterReplicationStartDate1Control = new FormControl(null);
filterReplicationStartDate2Control = new FormControl(null);
dpConfig: Partial<BsDatepickerConfig>;
minReplicationStartDate = new Date('01/01/2001');
maxReplicationStartDate = new Date('01/01/2025');
constructor(private fb: FormBuilder,
private translateService: TranslateService,
private store: Store<replicateModel.AppState>) {
super();
}
ngOnInit(): void {
this.replicationKeyStartList = this.getReplicationKeyStartList();
this.modelFilterForm = this.fb.group({
filterReplicationName: this.filterReplicationNameControl,
filterReplicationStatus: this.filterReplicationStatusControl,
filterReplicationStartDate: this.filterReplicationStartDateControl,
filterReplicationStartDate1: this.filterReplicationStartDate1Control,
filterReplicationStartDate2: this.filterReplicationStartDate2Control
});
this.subs.add(
this.translateService.translationChanged().subscribe(locale => {
this.dpConfig = Object.assign({},
{
dateInputFormat: this.translateService.translations.datePlaceholder,
minDate: this.minReplicationStartDate,
maxDate: this.maxReplicationStartDate,
showWeekNumbers: false,
containerClass: 'theme-wtw'
});
}),
this.store.pipe(select(replicateSelectors.getFilterTab)).subscribe(
filtered => {
this.filterTab = filtered;
this.performFilter();
}
),
this.store.pipe(debounceTime(ReplicateFilterComponent.DEBOUNCE_TIME), select(replicateSelectors.getModelName)).subscribe(
filtered => {
this.filterReplicationNameControl.setValue(filtered);
this.performFilter();
}
),
this.store.pipe(select(replicateSelectors.getModelReplicationStartDate)).subscribe(
filtered => {
this.filterReplicationStartDateControl.setValue(filtered);
this.performFilter();
}
),
this.store.pipe(select(replicateSelectors.getModelReplicationStartDate1)).subscribe(
filtered => {
if (filtered) {
const value = moment(filtered, 'YYYY-MM-DD').toDate();
this.filterReplicationStartDate1Control.setValue(value);
} else {
this.filterReplicationStartDate1Control.setValue(null);
}
this.performFilter();
}
),
this.store.pipe(select(replicateSelectors.getModelReplicationStartDate2)).subscribe(
filtered => {
if (filtered) {
const value = moment(filtered, 'YYYY-MM-DD').toDate();
this.filterReplicationStartDate2Control.setValue(value);
} else {
this.filterReplicationStartDate2Control.setValue(null);
}
this.performFilter();
}
),
this.filterReplicationNameControl.valueChanges.subscribe(
(value: string) => {
if (value) {
this.store.dispatch(replicateActions.setModelName(value));
} else if (value === '') {
this.store.dispatch(replicateActions.setModelName(null));
}
}
),
this.filterReplicationStatusControl.valueChanges.subscribe(
value => {
this.store.dispatch(replicateActions.setModelStatus(value));
}
),
this.filterReplicationStartDateControl.valueChanges.subscribe(
value => {
this.store.dispatch(replicateActions.setModelReplicationStartDate(value));
}
),
this.filterReplicationStartDate1Control.valueChanges.subscribe(
value => {
if (value) {
const date = moment(value).format('YYYY-MM-DD');
this.store.dispatch(replicateActions.setModelReplicationStartDate1(date));
} else {
this.store.dispatch(replicateActions.setModelReplicationStartDate1(null));
}
}
),
this.filterReplicationStartDate2Control.valueChanges.subscribe(
value => {
if (value) {
const date = moment(value).format('YYYY-MM-DD');
this.store.dispatch(replicateActions.setModelReplicationStartDate2(date));
} else {
this.store.dispatch(replicateActions.setModelReplicationStartDate2(null));
}
}
)
);
this.dpConfig = Object.assign({},
{
dateInputFormat: this.translateService.translations.datePlaceholder,
minDate: this.minReplicationStartDate,
maxDate: this.maxReplicationStartDate,
showWeekNumbers: false,
containerClass: 'theme-wtw',
});
}
/**
* Get All replication status name
*/
getReplicationKeyStartList(): Array<{ label: string, value: ReplicationStartDateValue }> {
return [
{ label: this.filterByReplicationStartBetweenText, value: 'Between' },
{ label: this.filterByReplicationStartBeforeText, value: 'Before' },
{ label: this.filterByReplicationStartAfterText, value: 'After' },
];
}
/**
* When a date is selected, sets the focus in the date's input field.
*
* @param input calendar input field
*/
onCalendarHidden(input: HTMLInputElement): void {
input.focus();
}
/**
* Clears the filter.
*/
clearFilter(): void {
this.store.dispatch(replicateActions.setModelName(null));
this.store.dispatch(replicateActions.setModelStatus('All'));
this.store.dispatch(replicateActions.setModelReplicationStartDate('Between'));
this.store.dispatch(replicateActions.setModelReplicationStartDate1(null));
this.store.dispatch(replicateActions.setModelReplicationStartDate2(null));
}
/**
* Returns true if the filter tab 'All' or 'Models' is selected and the Replication Type filter 'All' or 'Radar Model' is selected.
*
* @returns true if the filter tab 'All' or 'Models' is selected and the Replication Type filter 'All' or 'Radar Model' is selected.
*/
isTypeFilteredOnAllOrRadar(): boolean {
return (this.filterTab === 'all' || this.filterTab === 'models');
}
/**
* Form Filter function
*/
performFilter(): void {
const filters: Array<Filter<IReplication>> = [];
if (this.filterReplicationNameControl.value) {
filters.push((replication: IReplication) =>
replication.asset.name.toLocaleLowerCase().indexOf(this.filterReplicationNameControl.value.toLocaleLowerCase()) !== -1
);
}
if (this.isTypeFilteredOnAllOrRadar()) {
if (this.filterReplicationStartDateControl.value === 'Between') {
let date1 = this.filterReplicationStartDate1Control.value;
let date2 = this.filterReplicationStartDate2Control.value;
// If date2 is before date1 swap the dates
if (moment(date2).isBefore(date1)) {
const temp = date2;
date2 = date1;
date1 = temp;
}
if (date1 && date2) {
filters.push((model: IReplication) => {
const d1 = moment(date1).format('YYYY-MM-DD');
const d2 = moment(date2).format('YYYY-MM-DD');
const ks = moment(model.replication.requestedTimestampUtc).format('YYYY-MM-DD');
return moment(ks).isBetween(d1, d2, null, '[]');
});
}
} else if (this.filterReplicationStartDateControl.value === 'Before') {
const date1 = this.filterReplicationStartDate1Control.value;
if (date1) {
filters.push((model: IReplication) => {
const d1 = moment(date1).format('YYYY-MM-DD');
const ks = moment(model.replication.requestedTimestampUtc).format('YYYY-MM-DD');
return moment(ks).isSameOrBefore(d1);
});
}
} else if (this.filterReplicationStartDateControl.value === 'After') {
const date1 = this.filterReplicationStartDate1Control.value;
if (date1) {
filters.push((model: IReplication) => {
const d1 = moment(date1).format('YYYY-MM-DD');
const ks = moment(model.replication.requestedTimestampUtc).format('YYYY-MM-DD');
return moment(ks).isSameOrAfter(d1);
});
}
}
}
this.filters.emit(filters);
}
}
編輯#2 - 僅使用根存儲並記錄 state:
beforeEach(async () => {
await TestBed.configureTestingModule({
...
StoreModule.forRoot({
replication: replicationReducer
})
],
...
});
beforeEach(() => {
fixture = TestBed.createComponent(ReplicateFilterComponent);
component = fixture.componentInstance;
store = TestBed.inject(Store);
fixture.detectChanges();
store.subscribe(state => {
console.log(state);
});
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
我可以看到作為 state 的一部分記錄的“modelReplicationStartDate”屬性:
{replication: {…}}
replication:
filterTab: "all"
modelName: null
modelReplicationStartDate: "Between"
modelReplicationStartDate1: null
modelReplicationStartDate2: null
sortReplicateBy: "nameaz"
__proto__: Object
__proto__: Object
您同時提供了一個實際商店和一個模擬商店。 我對實際商店( StoreModule.forRoot()
)有更多經驗。 我會擺脫mockStore
。
擺脫這個:
provideMockStore({
initialState
})
在提供者數組中。
然后試試這個:
let store: Store<any>; // instead of MockStore
....
component = fixture.componentInstance;
store = TestBed.inject(Store); // instead of MockStore
fixture.detectChanges();
store.subscribe(state => {
// see the state of your store
console.log(state);
});
您遇到的問題很可能是您在組件中使用的選擇器未正確映射到 state 在商店中的位置。 查看console.log(state)
並查看您的選擇器如何從商店中獲取 state,我敢打賭,這就是Cannot read property modelReplicationStartDate of undefined
來源。
編輯:
問題很可能是這一行:
this.store.pipe(select(replicateSelectors.getModelReplicationStartDate)).subscribe(
filtered => {
this.filterReplicationStartDateControl.setValue(filtered);
this.performFilter();
}
),
與商店結構不正確的選擇器一起使用。
我認為您的商店必須是這樣的:
StoreModule.forRoot({
upload: uploadReducer,
[managementServiceFeatureKey]: managementServiceReducer,
assets: modelReducer,
// you need another feature here
replicate: replicateReducer,
})
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.