1

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.

1 Answer 1

1

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');
});
Sign up to request clarification or add additional context in comments.

1 Comment

You are a lifesaver! I implemented your suggestion and then mocked the initialState by importing a .js file with the contents of the store. Finally, I had to wrap the PlayerSelection component inside of a MemoryRouter. I want you to know that I really appreciate the time you took to reply to my question and point me in the right direction. Thank you SO much!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.