// src/reducers/FooReducer.js
export function FooReducer(state, action) {
switch (action.type) {
case 'update': {
return action.newState;
}
// ... other actions
default:
throw new Error('Unknown action type');
}
}
// src/components/BarComponent.js
export function BarComponent() {
const [state, dispatch] = useReducer(FooReducer, []);
return (
{state.map((item) => (<div />))}
);
}
// src/components/BarComponent.test.js
it('should render as many divs as there are items', () => {
act(() => {
const { result } = renderHook(() => useReducer(FooReducer, [1]));
const [, dispatch] = result.current;
wrapper = mount(<BarComponent />);
dispatch({type: 'update', newState: [1, 2, 3]});
});
expect(wrapper.find(div)).toHaveLength(3);
});
The above test example does not work, but serves to demonstrate what I am trying to achieve. and would actually render 0 div, as the initial state declared in the component contains 0 items.
How would I go about modifying a reducer's state or changing the initialState it is deployed with for testing purposes?
I am used to Redux reducers being used throughout multiple components, but useReducer needs a passed initialState... which raises the question: Is react-hook's reducer usable through multiple components as a single instance or will it always be 2 separate instances?
If you need to test a custom Hook, you can do so by creating a component in your test, and using your Hook from it. Then you can test the component you wrote. To reduce the boilerplate, we recommend using React Testing Library which is designed to encourage writing tests that use your components as the end users do.
Syntax. The useReducer Hook accepts two arguments. The reducer function contains your custom state logic and the initialState can be a simple value but generally will contain an object. The useReducer Hook returns the current state and a dispatch method.
The easiest way to test a custom react hook is to make use of the @testing-library/react-hooks library so you can test your react hooks in a similar way to a react component by passing the hook into the renderHook function and then asserting if the results are correct, similar to how you would in normal unit tests.
In your example, you're trying to test two things at the same time, which would be better off as separate tests: A unit test for your reducer, and a component test where the component uses the reducer.
- How would I go about modifying a reducer's state or changing the initialState it is deployed with for testing purposes?
Similar to a Redux reducers, your reducer is easily unit testable since you're exporting it as a pure function. Just pass in your initial state into the state
argument, and your action into action
:
it('returns new state for "update" type', () => {
const initialState = [1];
const updateAction = {type: 'update', newState: [1, 2, 3] };
const updatedState = fooReducer(initialState, udpateAction);
expect(updatedState).toEqual([1, 2, 3]);
});
You could also test it in the context of useReducer
if you prefer:
it('should render as many divs as there are items', () => {
act(() => {
const { result } = renderHook(() => useReducer(FooReducer, [1]));
const [state, dispatch] = result.current;
dispatch({type: 'update', newState: [1, 2, 3]});
});
expect(state).toEqual([1, 2, 3]);
// or expect(state).toHaveLenth(3) if you prefer
});
- I am used to Redux reducers being used throughout multiple components, but useReducer needs a passed initialState... which raises the question: Is react-hook's reducer usable through multiple components asa single instance or will it always be 2 separate instances?
Here's how useReducer
is different from Redux: You can reuse the reducer itself, but if you have multiple useReducer
s, the state
and dispatch
returned from each one, as well as the initial state, will be separate instances.
In order to test that your BarComponent updates when the reducer updates, you'll need a way to trigger dispatch
from within the component, since you're calling useReducer
inside your component. Here's an example:
export function BarComponent() {
const [state, dispatch] = useReducer(FooReducer, []);
const handleUpdate = () => dispatch({type: 'update', newState: [1, 2, 3]})
return (
<>
{state.map((item) => (<div key={item} />))}
<button onClick={handleUpdate}>Click to update state</button>
</>
);
}
it('should render as many divs as there are items', () => {
wrapper = mount(<BarComponent />);
expect(wrapper.find('div')).toHaveLength(0);
wrapper.find('button').simulate('click');
expect(wrapper.find('div')).toHaveLength(3);
});
This probably isn't very realistic, since I'm hardcoding the new array in the component itself, but hopefully it gives you the idea!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With