Есть ли идиоматический способ проверки вложенных ветвей штата?

Итак, скажем, что у меня есть редуктор с несколькими ветвями, но каждая ветвь достаточно похожа на генератор с функцией factory. Итак, я создаю один:

import { combineReducers } from 'redux'

const createReducerScope = scopeName => {
  const scope = scopeName.toUpperCase()

  const contents = (state = {}, action) => {
    switch (action.type) {
      case `${scope}_INSERT`:
        return { ...state, [action.id]: action.item }
      default:
        return state
    }
  }

  const meta = (state = {}, action) => {
    switch (action.type) {
      case `${scope}_REQUEST`:
        return { ...state, requesting: true }
      case `${scope}_REQUEST_FAILURE`:
        return {
          ...state,
          requesting: false,
          errorMessage: String(action.error)
        }
      case `${scope}_REQUEST_SUCCESS`:
        return {
          ...state,
          requesting: false,
          errorMessage: null
        }
      default:
        return state
    }
  }

  return combineReducers({ contents, meta })
}

Что я использую для компоновки большего дерева состояний корневого уровня:

const products = createReducerScope('products')
const orders = createReducerScope('orders')
const trades = createReducerScope('trades')

const rootReducer = combineReducers({ products, orders, trades })

Это должно дать мне граф состояний, который будет выглядеть следующим образом:

{
  products: { contents, meta },
  orders: { contents, meta },
  trades: { contents, meta }
}

Если бы я хотел протестировать это состояние, моим первым инстинктом является создание версии этой области в моем тестовом наборе, а затем проверка этого изолированного редуктора (только утверждение против ветвей contents и meta).

Усложнение заключается в том, что я пытаюсь также тестировать селектор, и все созданные мной селекторные схемы, кажется, предлагают эти две вещи:

  • Вы инкапсулируете свое состояние, выбирая селектор и редукторы в том же файле.
  • mapStateToProps нужно знать только две вещи: глобальное состояние и соответствующий селектор.

Итак, вот моя проблема: Сопряжение этих более или менее сложных редукторов с селекторами, зависящими от уровня корня, сделало мои тесты немного толстыми с шаблоном.

Не говоря уже о том, что записывающие селектора со знанием всего дерева чувствуют, что это бросает вызов точке попытки модульной редукции.

Я на 100% уверен, что мне не хватает чего-то очевидного, но я не могу найти какой-либо примерный код, который демонстрирует способ тестирования модульных редукторов и селекторов.

Если селектор, как правило, должен знать все глобальное состояние, но у вас есть редукторы, которые очень сложны, есть ли чистый, идиоматический подход к тестированию? Или, может быть, более сложный селекторный дизайн?

Ответ 1

Точка выделения селекторов и редукторов редукторы, а селекторы в одном файле должны работать в одной и той же форме состояния. Если вы разделите редукторы на несколько файлов для их компоновки, вы должны сделать то же самое для своих селекторов.

Вы можете увидеть пример этого в моей новой серии Egghead (видео 10 и 20 может быть особенно полезно).

Итак, ваш код должен быть больше похож на

const createList = (type) => {
  const contents = ...
  const meta = ...
  return combineReducers({ contents, meta })
}

// Use default export for your reducer
// or for a reducer factory function.
export default createList

// Export associated selectors
// as named exports.
export const getIsRequesting = (state) => ...
export const getErrorMessage = (state) => ...

Затем ваш index.js может выглядеть как

import createList, * as fromList from './createList'

const products = createList('products')
const orders = createList('orders')
const trades = createList('trades')

export default combineReducers({ products, orders, trades })

export const getIsFetching = (state, type) =>
  fromList.getIsFetching(state[type])

export const getErrorMessage = (state, type) =>
  fromList.getErrorMessage(state[type])

Таким образом, корневые селектора делегируют дочерние селекторы, так же как и корневые редукторы делегируют дочерние редукторы. В каждом файле state внутри селекторов соответствует state экспортированного редуктора, а детали реализации формы состояния не распространяются на другие файлы.

Наконец, для тестирования этих пакетов редуктора/селектора вы можете сделать что-то вроде

import createList, * as fromList from './createList')

describe('createList', () => {
  it('is not requesting initially', () => {
    const list = createList('foo')
    const state = [{}].reduce(list)
    expect(
      fromList.isRequesting(state)
    ).toBe(false)
  })

  it('is requesting after *_REQUEST', () => {
    const list = createList('foo')
    const state = [{}, { type: 'foo_REQUEST' }].reduce(list)
    expect(
      fromList.isRequesting(state)
    ).toBe(true)
  })
})