Reputation: 189
First off, please let me know if this is not the right place to ask this question or move it to the right place.
I am starting to write unit tests for some React components. One of them does not have any props but depends on a store
:
const PlayerSelector = () => {
const classes = useStyles();
const divRef = useRef(null);
const [{ playerList }, dispatch] = useStore();
...
where userStore()
comes from:
export const StateContext = createContext(null);
export const StoreProvider = ({ initialState, reducer, children }) => (
<StateContext.Provider
value={useReducer(reducer, initialState)}
>
{children}
</StateContext.Provider>
);
export const useStore = () => useContext(StateContext);
My problem is that when I try to render
PlayerSelector
in the test file, I run into an error because the test does not have access to that store:
it('loads component', () => {
const { queryByRole } = render(<PlayerSelector />);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
The result is TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))
pointing to this line:
const [{ firmList }, dispatch] = useStore();
After googling a lot, I found some helpful resources indicating how to mock context - but not how to do the same for a reducer. I do not know if I am overcomplicating things and should probably find another way to test, or if I am missing an obvious answer. In any event, I would appreciate any tips or direction you can share.
Upvotes: 1
Views: 1281
Reputation: 202801
When you are testing a component that consumes a context from a provider you also need to render the provider in order to provide the context. The render
takes a second options argument that can include a wrapper to wrap the component being tested.
Example:
const customWrapper = ({ children }) => <SomeProvider>{children}</SomeProvider>;
it('loads component', () => {
const { queryByRole } = render(
<PlayerSelector />,
{
wrapper: customWrapper
},
);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
With your redux-like provider you can create us an instance of your StoreProvider
to create a wrapper for testing.
it('loads component', () => {
const { queryByRole } = render(
<PlayerSelector />,
{
wrapper: ({ children }) => (
<StoreProvider
initialState={{}} // <-- any initial state object
reducer={rootReducerFunction} // <-- any reducer function
>
{children}
</StoreProvider>
),
},
);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
If you find yourself needing to write the wrapper utility a lot you can also create a custom render function, which more or less duplicates the above.
const StateProvider = ({ children }) => (
<StoreProvider
initialState={{}} // <-- any initial state object
reducer={rootReducerFunction} // <-- any reducer function
>
{children}
</StoreProvider>
);
export const renderWithState = (ui, options) => {
return render(ui, { wrapper: StateProvider, ...options });
}
test
import { renderWithState } from '../path/to/utils';
...
it('loads component', () => {
const { queryByRole } = renderWithState(<PlayerSelector />);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
Upvotes: 1