【问题标题】:Why are entities from createSelector returning null when tested in Jest?为什么在 Jest 中测试时 createSelector 中的实体返回 null?
【发布时间】:2022-09-29 03:37:07
【问题描述】:

我正在使用 Jest/Testing Library 编写单元测试。

当我渲染一个使用 redux/toolkit 的 createEntityAdapter 方法的组件时,它返回这个错误:

TypeError: Cannot read property \'id\' of undefined

      11 |   companySelectors.selectAll,
      12 |   (selectedCompanyId, entities) => {
    > 13 |     return entities.find((e) => e.id === selectedCompanyId);
         |                                   ^
      14 |   },
      15 | );

上面的代码是传入的 createSelector 参数的一部分。

这是选择器:

import { createSelector } from \'@reduxjs/toolkit\';
import { RootState } from \'src/store\';
import { companyAdapter } from \'./company.slice\';

export const companySelectors = companyAdapter.getSelectors<RootState>(
  (state) => state.company,
);

export const getSelectedCompany = createSelector(
  (state: RootState) => state.company.selectedId,
  companySelectors.selectAll,
  (selectedCompanyId, entities) => {
    return entities.find((e) => e.id === selectedCompanyId);
  },
);

这里是创建 companyAdapter 的地方:

    import { createEntityAdapter, createSlice } from \'@reduxjs/toolkit\';
    import { Company } from \'src/types/company\';
    import { getAllCashScenarios } from \'../cash-scenario/cash-scenario.thunk\';
    import {
      getAllCompanies,
      getAuthenticatedCompany,
      getCurrentCompany,
    } from \'./company.thunk\';
    
    export interface CompanyState {
      current: Company;
      selectedId: string;
      cashScenarios: number[];
    }
    
    export const companyAdapter = createEntityAdapter<Company>();
    
    const initialState = companyAdapter.getInitialState({
      current: undefined,
      selectedId:
        typeof window !== \'undefined\'
          ? window.localStorage.getItem(\'companyId\')
          : undefined,
      cashScenarios: [],
    } as CompanyState);
    
    export const slice = createSlice({
      name: \'company\',
      initialState,
      reducers: {
        selectCompany: (state, action) => {
          state.selectedId = action.payload;
          localStorage.setItem(\'companyId\', state.selectedId);
        },
      },
      extraReducers: (builder) => {
        builder.addCase(getCurrentCompany.fulfilled, (state, action) => {
          state.current = action.payload;
        });
    
        builder.addCase(getAuthenticatedCompany.fulfilled, (state, action) => {
          state.current = action.payload;
          state.selectedId = state.current.id;
          localStorage.setItem(\'companyId\', state.selectedId);
        });
    
        builder.addCase(getAllCompanies.fulfilled, (state, action) => {
          companyAdapter.upsertMany(state, action);
        });
    
        builder.addCase(getAllCashScenarios.fulfilled, (state, action) => {
          state.cashScenarios = action.payload.map((p) => p.id);
        });
      },
    });
    
    const { reducer } = slice;
    export default reducer;
    export const companyActions = slice.actions;

鉴于上面的代码,我如何确保 \'entities\' 对象中的各个实体不是未定义的?

当我渲染组件时,我在 Provider 中传入了一个模拟状态:

const mockState = company: {
    ids: [\'1234\', \'1235\'],
    entities: {
      1234: {
        id: \'1234\',
        name: \'Awesome Company\',
        createdDate: \'2021-01-01\',
        lastUpdated: \'2022-01-03\',
        dates: [\'Jan 2016\', \'Feb 2016\'],
      }
    },
    selectedId: \'1234\',
}

显然,selectedIdentities 已定义。

  • 嘿,你找到答案了吗?

标签: javascript reactjs redux react-redux redux-toolkit


【解决方案1】:

我有同样的问题,我想我找到了原因。就我而言,我创建商店,使用adapter.setAll 添加实体,但状态仍然为空:

// test.spec.js

const store = mockStore()

productsAdapter.upsertMany(store.getState().products, [...])


// I expect to receive filled collection, but get initial values
productsAdapter.getSelectors().selectAll(store.getState().products) // []
store.getState().products // { ids: [], entities: {} }

但它适用于带有 extraReducers 的应用程序:

// productsSlice.s

export const productsSlice = createSlice({
  // ...
  extraReducers: (builder) => {
    builder.addMatcher(api.endpoints.products.matchFulfilled, (state, { payload }) => {
      productsAdapter.setAll(state, payload)
    })
  },
})

所以问题就在潜意识里。在addMatcher 中,我们得到了状态的浸入式包装器,如果我们将相同的字段更改为状态,它将被更新。如果您看到setAll 方法(https://github.com/reduxjs/redux-toolkit/blob/master/packages/toolkit/src/entities/sorted_state_adapter.ts#L59)的源代码,它对状态对象进行了变异操作。

但在测试中,我们有一个具有只读属性的状态对象(没有 immer)。因此,当我们尝试设置数据时,我们实际上并没有做任何事情。

通过将集合作为初始状态传递来解决问题:

// test.spec.js

const store = makeStore({
  [productsSlice.name]: productsAdapter.setAll(
    productsSlice.getInitialState(),
    [...]
  ),
})

// get not empty array as expected
productsAdapter.getSelectors().selectAll(store.getState().products) // [...]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-24
    • 2019-10-03
    • 1970-01-01
    • 2018-04-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多