角单元测试模拟选择器不起作用 [英] Angular Unit Test Mock Selector not working

查看:71
本文介绍了角单元测试模拟选择器不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在我的单元测试中模拟一个选择器,如下所示:

I am trying to mock a selector in my unit test like below:

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

但是当我运行规范时,我得到的错误是

But when i run the spec i am getting the error as

TypeError:无法读取未定义的属性"length"

TypeError: Cannot read property 'length' of undefined

推荐答案

我认为问题在于使用provideMockStore模仿选择器.我可以看到您已经使用了很多基于ngRx选择器设置的this.selectedRows.length(例如在setEditBackgroundColor()中).

I think the issue is with the mocking of selector using provideMockStore. I can see that you have used a lot of this.selectedRows.length (such as in setEditBackgroundColor()) which is being set based on the ngRx Selector.

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')的预期输出进行尝试,我认为它应该可以工作.

Try this as per expected output of select('devices') and I think it should work.

另一方面,请尝试不要像在setEditBackgroundColor()和其他版本中那样从HTML进行函数调用,这会影响性能,并且会在每个ChangeDetection周期中调用它(尝试在此类方法中放入console.log) .他们将被多次呼叫.最好使用一些map设置对象属性,然后在HTML上呈现

On side note, try not to make function calls from HTML as you have done in setEditBackgroundColor() and others, it impacts the performance and it'll be called in every ChangeDetection cycle (try putting console.log in such methods) . They will be called several times. Better use some map to set a object property and then render it on HTML

这篇关于角单元测试模拟选择器不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆