So let's say that I have a reducer which has several branches, but each branch is similar enough to generate with a factory function. So, I create one:
import { bineReducers } 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 bineReducers({ contents, meta })
}
Which I use to pose a larger root-level state tree:
const products = createReducerScope('products')
const orders = createReducerScope('orders')
const trades = createReducerScope('trades')
const rootReducer = bineReducers({ products, orders, trades })
That should give me a state graph which would look like this:
{
products: { contents, meta },
orders: { contents, meta },
trades: { contents, meta }
}
If I wanted to test this state, my first instinct is to create a version of this scope in my test suite, and then test that isolated reducer (just asserting against the contents
, and meta
branches).
The plication here is that I'm trying to also test selectors, and all of the selector designs I've read seem to suggest these two things:
- You encapsulate your state by colocating selectors and reducers in the same file.
mapStateToProps
should only need to know two things, the global state and a relevant selector.
So here's my problem: Pairing together these more or less posed reducers with root-level-concerned selectors has made my tests a little thick with boilerplate.
Not to mention that hard-writing selectors with knowledge of the entire tree feels like it defies the point of the reducer modularity attempt.
I'm 100% sure I'm missing something obvious, but I can't really find any example code which demonstrates a way to test modular reducers and selectors.
If a selector generally should know the entire global state, but you have reducers which are highly posed, is there a clean, idiomatic approach to testing that? Or maybe a more posable selector design?
So let's say that I have a reducer which has several branches, but each branch is similar enough to generate with a factory function. So, I create one:
import { bineReducers } 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 bineReducers({ contents, meta })
}
Which I use to pose a larger root-level state tree:
const products = createReducerScope('products')
const orders = createReducerScope('orders')
const trades = createReducerScope('trades')
const rootReducer = bineReducers({ products, orders, trades })
That should give me a state graph which would look like this:
{
products: { contents, meta },
orders: { contents, meta },
trades: { contents, meta }
}
If I wanted to test this state, my first instinct is to create a version of this scope in my test suite, and then test that isolated reducer (just asserting against the contents
, and meta
branches).
The plication here is that I'm trying to also test selectors, and all of the selector designs I've read seem to suggest these two things:
- You encapsulate your state by colocating selectors and reducers in the same file.
mapStateToProps
should only need to know two things, the global state and a relevant selector.
So here's my problem: Pairing together these more or less posed reducers with root-level-concerned selectors has made my tests a little thick with boilerplate.
Not to mention that hard-writing selectors with knowledge of the entire tree feels like it defies the point of the reducer modularity attempt.
I'm 100% sure I'm missing something obvious, but I can't really find any example code which demonstrates a way to test modular reducers and selectors.
If a selector generally should know the entire global state, but you have reducers which are highly posed, is there a clean, idiomatic approach to testing that? Or maybe a more posable selector design?
Share Improve this question edited Jun 5, 2016 at 22:14 Dan Abramov 268k87 gold badges416 silver badges518 bronze badges asked Jun 5, 2016 at 21:57 ironchamberironchamber 2042 silver badges10 bronze badges1 Answer
Reset to default 14The point of colocating selectors and reducers is reducers and selectors in one file should operate on the same state shape. If you split reducers into multiple files to pose them, you should do the same for your selectors.
You can see an example of this in my new Egghead series (videos 10 and 20 might be especially useful).
So your code should be more like
const createList = (type) => {
const contents = ...
const meta = ...
return bineReducers({ 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) => ...
Then, your index.js
might look like
import createList, * as fromList from './createList'
const products = createList('products')
const orders = createList('orders')
const trades = createList('trades')
export default bineReducers({ products, orders, trades })
export const getIsFetching = (state, type) =>
fromList.getIsFetching(state[type])
export const getErrorMessage = (state, type) =>
fromList.getErrorMessage(state[type])
This way the root selectors delegate to the child selectors, just like the root reducers delegate to the child reducers. In every file, state
inside selectors corresponds to the state
of the exported reducer, and state shape implementation details don’t leak to another files.
Finally, for testing those reducer/selector bundles you could do something like
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)
})
})