Search code examples
reactjsreduxjestjsreact-routerreact-testing-library

testing components wrapped in redux and router with jest


I am trying to test a component which uses both react redux and react router. so when you click a card in the main page it routes to this particular component. here is the card component

here is the page I am routing to

const MovieDetail = () => {
  const { id } = useParams();
  const dispatch = useDispatch();
  const movie = useSelector((state) => state.movies.selectedItem);
  const crews = useSelector((state) => state.movies.detailList);

  useEffect(() => {
    dispatch(fetchDetail(id, "movies", movieActions));
    dispatch(fetchDetailList(id, "movies", "crews", movieActions));
  }, [dispatch, id]);

  return (
   <Box>
  <Card
    sx={{
      display: "flex",
      justifyContent: "space-between",
      gap: "80px",
      margin: "80px 10px 10px 10px",
    }}
  >
    <Box sx={{ margin: "10px" }}>
      <Typography sx={{ fontSize: "25px" }}>{movie.title}</Typography>
      <Box
        sx={{
          paddingLeft: "3px",
          marginTop: "20px",
          fontSize: "24px",
          display: "flex",
          justifyContent: "space-between",
        }}
      >
        <span>
          <img
            src="https://img.icons8.com/tiny-color/16/000000/star.png"
            alt="star icon"
          />
          IMDB Rating <i></i>: {movie.imdbRating}
        </span>
        <span>
          IMDB Votes <i></i>: {movie.imdbVotes}
        </span>
        <span>
          Runtime <i></i> : {movie.runtime}
        </span>
        <span>
          Year <i></i> : {movie.year}
        </span>
      </Box>
      <Typography sx={{ margin: "20px" }}>{movie.plot}</Typography>
    </Box>
  </Card>
</Box>
  );
};

export default MovieDetail;

this is my handler for jest

rest.get("http://localhost:8080/api/movies/tt0389790", (req, res, ctx) => {
    return res(
      ctx.json({
        id: "tt0389790",
        title: "Bee Movie",
        year: "2007",         
        country: "United States",
        poster:
          "https://m.media-amazon.com/images/M/MV5BMjE1MDYxOTA4MF5BMl5BanBnXkFtZTcwMDE0MDUzMw@@._V1_SX300.jpg",
      })
    );
  }),

this is my custom wrapper

 export function renderWithProviders(
  ui,
  {
    route = '/',
    history = createMemoryHistory({initialEntries: [route]}),
    preloadedState = {},
    storeUtils = store(preloadedState),
    ...renderOptions
  } = {}
) {
  function Wrapper({ ui}) {
    return (
      <Provider store={storeUtils}>
        <Router history={history}>{ui}</Router>
      </Provider>
    );
  }
  return { store, ...render(ui, { wrapper: Wrapper, history, ...renderOptions }) };
}

and finally this is my test

test("data is shown properly", async () => {
  renderWithProviders(
    <Route path="/api/movies/tt0389790">
      {props =><MovieDetail {...props} />}
    </Route>,
    {
      route: '/api/movies/tt0389790',
    },
  );

  const moviePoster = await screen.findByAltText("Bee Movie");
  expect(moviePoster).toBeInTheDocument();

  const movieTitle = await screen.findByText("Bee Movie");
  expect(movieTitle).toBeInTheDocument();

  const movieYear = await screen.findByText("2007");
  expect(movieYear).toBeInTheDocument();
});

but nothing works and I am getting errors like

Uncaught [TypeError: Cannot read properties of undefined (reading 'pathname')]

this is my route

 <Route path="/movies/:id" element={<MovieDetail />} />

Solution

  • after trying multiple ways I decided to not wrapp Router around all of my test components. the handlers mock server for requests with ids should look like this:

    "http://localhost:8080/api/movies/:id"
    

    which I made a mistake by writting it like this at first:

    "http://localhost:8080/api/movies/tt0389790"
    

    as for the test I used:

    createMemoryHistory()
    

    this is how I used it in the test:

    const history = createMemoryHistory();
      renderWithProviders(
        <Router location={history.location} navigator={history}>
          <MovieDetail />
        </Router>
      );
    

    and changed my custom render to this:

    export function renderWithProviders(
      ui,
      {
        preloadedState = {},
        storeUtils = store(preloadedState),
        ...renderOptions
      } = {}
    ) {
      function Wrapper({ children }) {
        return <Provider store={storeUtils}>{children}</Provider>;
      }
      return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
    }
    

    after all these the test passed perfectly.