角单元测试模拟选择器不起作用
2020-09-21
2951
我试图在我的单元测试中模拟一个选择器,如下所示:
describe('Device List Component', () => {
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;
let deviceServiceMock: any;
let mockStore: MockStore<any>;
let devices;
beforeEach(async(() => {
deviceServiceMock = jasmine.createSpyObj('DevicesService', ['fetchDevices']);
deviceServiceMock.fetchDevices.and.returnValue(of(deviceState()));
TestBed.configureTestingModule({
declarations: [
ListComponent,
MockComponent(DataGridComponent),
],
imports: [
RouterTestingModule,
MockModule(SharedModule),
ToastrModule.forRoot({
preventDuplicates: true,
closeButton: true,
progressBar: true,
}),
TranslateModule.forRoot({
loader: { provide: TranslateLoader, useClass: JsonTranslationLoader },
}),
],
providers: [
{ provide: DevicesService, useValue: deviceServiceMock },
{ provide: ColumnApi, useClass: MockColumnApi },
provideMockStore(),
],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance;
mockStore = TestBed.get(MockStore);
component.columnApi = TestBed.get(ColumnApi);
devices = mockStore.overrideSelector('devices', deviceState());
fixture.detectChanges();
});
});
这是组件文件
export class ListComponent implements OnInit, OnDestroy {
columnDefs: DeviceColumns[];
defaultColumnDefs: any;
gridApi: any;
columnApi: any;
overlayNoRowsTemplate: string;
rowData: DeviceData[] = [];
pagination: DevicePagination;
globalSearch: string;
hasFloatingFilter: boolean;
frameworkComponents: any;
dropdownSettings: any = {};
dropDownList: Columns[] = [];
selectedItems: Columns[] = [];
shuffledColumns: any = [];
selectedRows: DeviceData[] = [];
rowSelection: string;
bsModalRef: BsModalRef;
isColumnsSorting: boolean;
hasRowAnimation = true;
multiSortKey = 'ctrl';
changeDetectorRef: ChangeDetectorRef;
deviceDeleteSubscription: Subscription;
currentPage = new Subject<number>();
search = new Subject<string>();
subscription: Subscription;
constructor(
private router: Router,
private devicesService: DevicesService,
public store: Store<any>,
private toast: ToastrService,
private ngZone: NgZone,
private translateService: TranslateService,
private globalTranslate: GlobalLanguageService,
private modalService: BsModalService,
changeDetectorRef: ChangeDetectorRef
) {
this.translateService.stream(['DEVICES.LIST', 'MULTISELECT']).subscribe((translations) => {
const listTranslations = translations['DEVICES.LIST'];
const multiSelectTranslations = translations['MULTISELECT'];
this.overlayNoRowsTemplate = `<span class="ag-overlay-loading-center">${listTranslations.NODEVICE}</span>`;
this.dropdownSettings.selectAllText = multiSelectTranslations.SELECTALL;
this.dropdownSettings.unSelectAllText = multiSelectTranslations.DESELECTALL;
});
this.changeDetectorRef = changeDetectorRef;
this.translateService.onLangChange.subscribe(() => {
this.gridApi && this.gridApi.refreshHeader();
});
}
ngOnInit() {
this.loadStore();
this.initializeColumns();
this.pageSearch();
this.dropdownSettings = {
singleSelection: false,
idField: 'field',
textField: 'key',
selectAllText: 'Select All',
unSelectAllText: 'UnSelect All',
itemsShowLimit: 3,
allowSearchFilter: true,
enableCheckAll: true,
};
this.store.dispatch(new DevicesActions.ClearCurretDevice());
}
loadStore() {
this.store.pipe(select('devices')).subscribe((val) => {
const deviceList = val.devices.map((d) => {
return {
...d,
is_online: d.is_online ? 'Active' : 'Inactive',
};
});
this.rowData = deviceList;
this.pagination = val.pagination;
this.globalSearch = val.globalSearch;
this.hasFloatingFilter = val.hasFloatingFilter;
this.dropDownList = val.shuffledColumns;
this.selectedItems = val.shuffledColumns.filter((column: Columns) => !column.hide);
this.selectedRows = val.selectedRows;
});
}
initializeColumns() {
this.columnDefs = [
{
headerName: 'S.No',
translateKey: 'DEVICES.LIST.SNO',
width: 100,
resizable: false,
sortable: false,
suppressSizeToFit: true,
valueGetter: (args) => this.getId(args),
checkboxSelection: (params) => {
console.log('params.columnApi.getRowGroupColumns()', params.columnApi.getRowGroupColumns());
return params.columnApi.getRowGroupColumns().length === 0;
},
headerCheckboxSelection: (params) => {
return params.columnApi.getRowGroupColumns().length === 0;
},
},
...gridColumns,
];
this.columnDefs = map(this.columnDefs, (columnDef) => {
return extend({}, columnDef, { headerValueGetter: this.localizeHeader.bind(this) });
});
this.shuffledColumns.push(this.columnDefs[0]);
this.dropDownList.forEach((column, colIndex) => {
this.columnDefs.forEach((data) => {
if (data.field === column.field) {
data.hide = column.hide;
data.sort = column.sort;
data.width = column.width;
data.minWidth = column.minWidth;
this.shuffledColumns.splice(colIndex + 1, 0, data);
}
});
});
this.columnDefs = this.shuffledColumns;
this.rowSelection = 'multiple';
this.defaultColumnDefs = {
suppressMenu: true,
suppressMovable: true,
sortable: true,
resizable: true,
};
this.frameworkComponents = { FloatingFilterComponent: FloatingFilterComponent };
}
localizeHeader(params: any) {
return this.globalTranslate.getTranslation(params.colDef.translateKey);
}
getId(args: any): any {
return (
this.pagination.per_page * this.pagination.prev_page + parseInt(args.node.rowIndex, 10) + 1
);
}
pageSearch() {
this.subscription = this.search.subscribe((value) => {
this.store.dispatch(new DevicesActions.GlobalSearch(value));
if (value.length === 0) {
this.clearSelectedRows();
this.loadData();
}
});
}
OnGridReady(params) {
this.gridApi = params.api;
this.columnApi = params.columnApi;
this.loadData();
}
loadData() {
this.devicesService.fetchDevices(this.gridApi);
}
gotoAddDevice() {
this.router.navigate(['/devices/new']);
}
searchDevices() {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(1));
this.clearSelectedRows();
this.loadData();
}
clearSelectedRows() {
this.store.dispatch(new DevicesActions.ClearSelectedRows());
}
onItemSelect(item: DropDownColumns) {
this.store.dispatch(new DevicesActions.ColumnSelect(item.field));
this.columnApi.setColumnVisible(item.field, true);
}
onSelectAll(items: any) {
this.store.dispatch(new DevicesActions.ColumnsSelectAll());
items.map((item) => this.columnApi.setColumnVisible(item.field, true));
}
onItemUnSelect(item: DropDownColumns) {
this.store.dispatch(new DevicesActions.ColumnDeSelect(item.field));
this.columnApi.setColumnVisible(item.field, false);
}
onDeSelectAll() {
this.store.dispatch(new DevicesActions.ColumnsDeSelectAll());
this.dropDownList.map((item) => this.columnApi.setColumnVisible(item.field, false));
}
SortedColumns(params: SortedColumns[]) {
const columnsId = [];
params.map((param: SortedColumns) => {
columnsId.push(param.id);
});
const shuffledColumns = columnsId.map((columnId) =>
this.dropDownList.find((data) => data.field === columnId)
);
this.store.dispatch(new DevicesActions.ShuffledColumns(shuffledColumns));
this.columnApi.moveColumns(columnsId, 1);
}
hasDevices() {
return this.rowData.length > 0 ? true : false;
}
updatePage() {
if (this.pagination.current_page.toString() === '') {
this.pagination.current_page = 1;
}
this.store.dispatch(new DevicesActions.UpdateCurrentPage(this.pagination.current_page));
this.clearSelectedRows();
this.loadData();
}
previousPage() {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(this.pagination.prev_page));
this.clearSelectedRows();
this.loadData();
}
firstPage() {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(1));
this.clearSelectedRows();
this.loadData();
}
lastPage() {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(this.pagination.total_pages));
this.clearSelectedRows();
this.loadData();
}
nextPage() {
if (!this.pagination.is_last_page) {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(this.pagination.next_page));
this.clearSelectedRows();
this.loadData();
}
}
toggleFloatingFilter() {
this.hasFloatingFilter = !this.hasFloatingFilter;
this.store.dispatch(new DevicesActions.UpdateFloatingFilter(this.hasFloatingFilter));
this.clearSelectedRows();
this.gridApi.setRowData(this.rowData);
if (!this.hasFloatingFilter) {
this.gridApi.setFilterModel(null);
this.store.dispatch(new DevicesActions.ClearColumnSearch());
this.loadData();
}
setTimeout(() => {
this.gridApi.refreshHeader();
}, 0);
window.location.reload();
}
isSortingEnabled() {
this.isColumnsSorting = this.dropDownList.some((column) => column.sort !== '');
return this.isColumnsSorting;
}
setSortingBackgroundColor() {
return this.isColumnsSorting ? COLOR_PRIMARY : COLOR_SECONDARY;
}
setSortingIconColor() {
return this.isColumnsSorting ? ICON_ENABLED : ICON_DISABLED;
}
clearSort() {
this.gridApi.setSortModel(null);
this.store.dispatch(new DevicesActions.ClearColumnsSort());
this.loadData();
}
resizeColumns() {
const allColumnIds = [];
this.columnApi.getColumnState().forEach((column) => {
allColumnIds.push(column.colId);
});
this.columnApi.autoSizeColumns(allColumnIds, false);
}
onRowDataChanged() {
if (this.gridApi) {
this.gridApi.forEachNode((node: any) => {
const selectNode = this.selectedRows.some((row) => row.id === node.data.id);
if (selectNode) {
node.setSelected(true);
}
});
}
}
onSelectionChanged() {
this.selectedRows = this.gridApi.getSelectedRows();
console.log('selected rows', this.selectedRows);
this.store.dispatch(new DevicesActions.UpdateSelectedRows(this.selectedRows));
this.changeDetectorRef.detectChanges();
}
onSortChanged(params) {
this.store.dispatch(new DevicesActions.UpdateColumnsSort(params));
this.clearSelectedRows();
this.loadData();
}
onColumnResized() {
const updatedColumns: ColumnWidth[] = [];
this.columnApi.getColumnState().forEach((column) => {
updatedColumns.push({ field: column.colId, width: column.width });
});
this.store.dispatch(new DevicesActions.UpdateColumnWidth(updatedColumns));
}
gotoDetailView(params: any) {
const id = params.id;
this.ngZone.run(() => this.router.navigate(['/devices', id]));
}
isDeleteEnabled() {
return this.selectedRows.length === 0 ? true : false;
}
setDeleteBackgroundColor() {
return this.selectedRows.length !== 0 ? COLOR_PRIMARY : COLOR_SECONDARY;
}
setDeleteIconColor() {
return this.selectedRows.length !== 0 ? ICON_ENABLED : ICON_DISABLED;
}
openModal() {
const initialState = {
title: 'Delete Device',
message: 'Do you really want to delete the device? This process cannot be undone',
};
if (this.selectedRows.length > 1) {
(initialState.title = 'Delete Devices'),
(initialState.message = `Do you really want to delete ${this.selectedRows.length} devices? This process cannot be undone`);
}
this.bsModalRef = this.modalService.show(ModalDeleteComponent, { initialState });
this.bsModalRef.content.delete.subscribe((canDelete: boolean) => {
if (canDelete) {
this.deleteDevices();
}
this.bsModalRef.hide();
});
}
ngOnDestroy() {
this.deviceDeleteSubscription?.unsubscribe();
}
deleteDevices() {
const selectedIds = this.selectedRows.map((row) => row.id).toString();
const params = {
ids: selectedIds,
};
this.deviceDeleteSubscription = this.devicesService.deleteDevices(params).subscribe(
(data) => {
const ids = selectedIds.split(',').map(Number);
this.clearSelectedRows();
this.store.dispatch(new DevicesActions.DeleteDevices(ids));
if (this.rowData.length === 0) {
this.store.dispatch(new DevicesActions.UpdateCurrentPage(1));
this.loadData();
}
this.toast.success('Deleted successfully');
setTimeout(() => {
window.location.reload();
}, 500);
},
(error) => {
this.toast.error(error.message);
}
);
}
editConfiguration() {
this.store.dispatch(new DevicesActions.SetEditDevice(this.selectedRows[0]));
this.router.navigate(['/devices', 'edit', this.selectedRows[0].id]);
}
isEditEnabled() {
return this.selectedRows.length !== 1 ? true : false;
}
setEditBackgroundColor() {
return this.selectedRows.length === 1 ? COLOR_PRIMARY : COLOR_SECONDARY;
}
setEditIconColor() {
return this.selectedRows.length === 1 ? ICON_ENABLED : ICON_DISABLED;
}
}
但是当我运行规范时,我得到的错误是
TypeError: Cannot read property 'length' of undefined
2个回答
我认为问题在于使用
provideMockStore
模拟选择器。我可以看到您使用了很多
this.selectedRows.length
(例如在
setEditBackgroundColor()
中),这些是根据 ngRx 选择器设置的。
providers: [
{ provide: DevicesService, useValue: deviceServiceMock },
{ provide: ColumnApi, useClass: MockColumnApi },
provideMockStore({
selectors: [
{
selector: selectLoginPagePending,
value: true
}
]
})
],
对于类似这样的选择器:
export const selectLoginPagePending = createSelector(
selectLoginPageState,
(state: State) => state.pending;
);
按照
select('devices')
的预期输出尝试此操作,我认为它应该可以工作。
附注:尽量不要像在
setEditBackgroundColor()
和其他方法中那样从 HTML 进行函数调用,这会影响性能,并且会在每个 ChangeDetection 周期中调用它(尝试将
console.log
放入此类方法中)。它们将被调用多次。最好使用一些
map
来设置对象属性,然后在 HTML 上呈现它
Shashank Vivek
2020-09-21
我没有使用选择器,而是使用如下所示的初始值设置了商店
const mockInitialAppState = {
devices: deviceState(),
};
beforeEach(() => {
deviceServiceMock = jasmine.createSpyObj('DevicesService', ['fetchDevices', 'deleteDevices']);
deviceServiceMock.fetchDevices.and.returnValue(of(deviceState()));
deviceServiceMock.deleteDevices.and.returnValue(of({}));
TestBed.configureTestingModule({
declarations: [ListComponent, MockComponent(DataGridComponent)],
imports: [
RouterTestingModule,
MockModule(SharedModule),
ToastrModule.forRoot({
preventDuplicates: true,
closeButton: true,
progressBar: true,
}),
TranslateModule.forRoot({
loader: { provide: TranslateLoader, useClass: JsonTranslationLoader },
}),
],
providers: [
{ provide: DevicesService, useValue: deviceServiceMock },
{ provide: ColumnApi, useClass: MockColumnApi },
{ provide: GridApi, useClass: MockGridApi },
provideMockStore({
initialState: { ...mockInitialAppState },
}),
],
}).compileComponents();
});
这使测试通过。绕过初始状态,我不需要初始化 selectedRows
Nidhin Kumar
2020-09-24