Hi guys,
I'm refactoring my code by splitting a huge reducer into smaller ones, each one managing its own part of the state. Exactly like the docs say.
However, even if I'm able to test each sub-reducer separately, I don't know how to test the root reducer. As it's just a "dispatcher" for sub-reducers, how should I do ? The examples from the doc website shows how to test a sub-reducer (the todos reducer), but not the root one.
I'm asking because as I have tests to help me during this refactoring, I realise that my tests pass when my sub-reducer is green, but before I even "plugged" it into the root one. As I'm the only developer I'll be ok, but I'm not very confortable with the idea that my tests are green whereas my root reducer isn't delegating to the proper root reducer.
As sub-reducers manage part of the state for specific actions, I can't see how to test it apart from replaying the same specific actions from the top and ensuring the sub-reducers are called. It'll be a lot of copy-paste and boilerplate, isn't it ? Do you have a better idea ?
Thx in advance !
What exactly do you want to test? I would probably write a “smoke test” that makes sure that a store can be created with this reducer, that initial state contains the keys for all child reducers I expect to be mounted, and that dispatching an action delegates control to the child reducer.
Something like
(reducers)
let rootReducer = combineReducers({ counter, todo })
(tests)
let store = createStore(rootReducer)
// check that initial state of the root reducer matches
// what child reducers return given an empty action
expect(store.getState().counter).toEqual(counter(undefined, {}))
expect(store.getState().todos).toEqual(todos(undefined, {}))
// alternatively you can test values explicitly although this
// couples this test to child reducer impl details:
expect(store.getState().counter).toEqual(0)
expect(store.getState().todos).toEqual([])
// now you can do a similar “smoke test” to check
// that child reducers handle an action
let action = { type: 'INCREMENT' }
store.dispatch(action)
expect(store.getState().counter).toEqual(counter(undefined, action))
expect(store.getState().todos).toEqual(todos(undefined, action))
// alternatively you can test values explicitly although this
// couples this test to child reducer impl details:
expect(store.getState().counter).toEqual(1)
expect(store.getState().todos).toEqual([])
I think this qualifies as a good enough smoke test for the root reducer.
Yep, good enough. Agree with you, testing with explicit values couples too much with the child reducer.
Side question (completely unrelated) : you removed the space before the ? in the issue title. Is it proper English ? Do you have a link so that I learn this grammatical rule ? Thx haha :smile:
@DjebbZ Seems like it's a mistake:
http://english.stackexchange.com/questions/4645/is-it-ever-correct-to-have-a-space-before-a-question-or-exclamation-mark
https://en.wikipedia.org/wiki/Question_mark#Stylistic_variants
Wiki says: in the English language orthography no space is allowed in front of the question mark
Ok, I understand, it's because in French the space is mandatory. Good to know !
@DjebbZ, if I may correct you:
- Good to know !
+ Good to know!
:joy:
Well done, Pr. @brikou :wink:
It depends on your application and your usage, but overall I don't think testing reducers and actions by themselves has much value in a lot of cases.
The testing approach we've been taking for our app is basically to create a test store instance, dispatch actions, and use the selectors for that module / thing to make sure the output is correct. Basically, use the module's public interface to make sure it behaves as expected.
// Creates a store instance with in-memory router and a fake window.fetch function.
// You'll usually have middleware that does more stuff, so having a way to create a store
// that behaves close to how your actual store behaves makes sense.
const store = createAppTestStore()
// fooCount is a selector
// First, we're checking that its initial state is what we expect
expect(fooCount(store.getState())).to.equal(0)
// incrementFoo is an action creator
store.dispatch(incrementFoo())
// Finally we're making sure our expected change happened
expect(fooCount(store.getState())).to.equal(1)
A fast and effective way out is the use of 'store test' or 'integration test' of the various redux parts. I needed to write a rootReducer test and by the time I wrote a single integration test, my coverage reporter showed a 100% coverage for the rootReducer.
import { createStore } from 'redux';
import rootReducer from '../reducers/rootReducer';
import * as recipeActions from '../actions/recipeActions';
describe('Store', () => {
it('should handle recipe creation', () => {
const store = createStore(rootReducer, []);
const recipe = { recipeName: 'fruitties' };
const action = recipeActions.addRecipeSuccess(recipe);
store.dispatch(action);
const actual = store.getState().userRecipes[0];
const expected = { recipeName: 'fruitties' };
expect(actual).toEqual(expected);
});
});
__Note:__
__userRecipes__ is the alias for the reducer that handles recipe creation action.
Most helpful comment
What exactly do you want to test? I would probably write a “smoke test” that makes sure that a store can be created with this reducer, that initial state contains the keys for all child reducers I expect to be mounted, and that dispatching an action delegates control to the child reducer.
Something like
(reducers)
(tests)
I think this qualifies as a good enough smoke test for the root reducer.