react router v6에서 react testing libarary 사용하기

January 28, 2023

현재 수행 중인 프로젝트(React)에 테스트 코드를 도입하기 위해서 공부하고 있습니다. react-testing-library라는, 이름에서도 유추할 수 있듯이 react에 최적화된 테스트용 라이브러리를 이용하여 스터디를 하고 있는데요, 자세한 내용은 추후 포스팅하도록 하겠습니다.

이번 포스팅을 시작하게 된 계기는, 위 스터디를 진행하면서 router와 관련된 테스트를 하다가 부딪힌 문제들에 대한 해결 방법을 기록해 두고 싶어서입니다. 현재 ‘스무디 한 잔 마시며 끝내는 리액트 + TDD’라는 책으로 공부 중인데요, 해당 책에서 예제로 나오는 부분은 router v5 기반으로 작성되었기 때문에 최신버전인 v6를 설치하여 실습을 하다 보니 다른 점이 많았습니다.

각설하고 제가 부딪혔던 문제들에 대해 기록해 보겠습니다.

Switch → Routes

기존 v5 버전에서는 route 요소들의 상위를 Switch 컴포넌트로 감쌌으나, 이제 명칭이 Routes로 변경되었습니다.

exact path → path

Route exact path로 사용하던 부분에서 exact가 제거되었습니다.

component → element

component로 렌더링하거나 또는 <Route>{children}</Route> 형태로 사용하던 것을 이제는 element라는 요소로 사용할 수 있습니다.

세 가지 변경 사항을 예시 비교를 통해 보여드리도록 하겠습니다.

v5

import { Switch } from "react-router-dom"

const Test = () => {
  return (
    <Switch>
      <Route exact path="/" component={<Home />} />
      <Route path="/add">{<Add />}</Route>
    </Switch>
  )
}

export default Test

v6

import { Routes } from "react-router-dom"

const Test = () => {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/add" element={<Add />} />
    </Routes>
  )
}

export default Test

useHistory → useNavigate

기존에는 페이지 이동을 위해 useHistory를 많이 사용하였으나, 이제 useNavigate라는 이름으로 변경되었습니다. 사용법 또한 약간씩 달라졌습니다.

v5 버전에서는 history에서 push, replace, go 등을 사용하였다면, navigate에서는 이동 및 back 시에는 navigate만 사용하도록 변경되었고, replacestate는 옵션으로 사용하도록 변경되었습니다.

v5

import { useHistory } from "react-router-dom"

const Test = () => {
  const history = useHistory()
  history.push("/")
  history.replace("/")
  history.go(-1)
}

export default Test

v6

import { useNavigate } from "react-router-dom"

const Test = () => {
  const navigate = useHistory()
  navigate("/")
  navigate("/", { replace: true, state: { ...state } })
  navigate(-1)
}

export default Test

MemoryRouter

사실 여기까지는 검색으로도 쉽게 알 수 있는 내용이고, 제가 적은 것 이외에도 중첩 라우팅, useRoutes 등 몇 가지 변경 사항이 더 있는데요,

실습 중인 코드에서는 useParams를 이용하고 있는데, 이것을 이용하면 다음과 같은 코드에서 id를 쉽게 추출할 수 있습니다.

// App.tsx
...
<Route path="/detail/:id" element={<Detail />} />
...

// Detail.tsx
const params = useParams();
const id = Number(params.id);

이렇게 useParams를 이용해서 구현한 컴포넌트를 테스트하기 위해서는 history를 제어할 수 있는 router를 사용해야 합니다.

테스트 코드에서는 실제로 경로 변경을 할 수 없기 때문에 기존에는 react-router가 테스트용으로 제공하는 createMemoryHistory를 사용해서 코드를 작성했는데요,

v5용으로 작성된 예제를 v6에서 어떻게 변경해야 하는지는 쉽게 찾을 수 없었습니다.

검색을 계속하다가 Testing Library 공식 홈페이지에서 MemoryRouter라는 것을 알게 되었습니다.

MemoryRoute는 위치를 내부적으로 저장하는 라우터로, history를 제어할 수 있기 때문에 테스트용으로 최적화되어 있는 라우터입니다.

이번에도 예제를 통해 비교해 보도록 하겠습니다.

v5

import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
...

describe('<PageHeader />', () => {
	it('renders component correctly', () => {
		const history = createMemoryHistory();
		history.push('/');

		const { container } = render(
			<Router history={history}>
				<PageHeader />
			</Router>
		);
		...
	});
});

v6

import { MemoryRouter } from 'react-router-dom';
...

describe('<PageHeader />', () => {
	it('renders component correctly', () => {
		const path = '/';

    const { container } = render(
      <MemoryRouter initialEntries={[path]}>
        <PageHeader />
      </MemoryRouter>
    );
		...
	});
});

v5에서는 Router로 감싼 부분에 history(createMemoryHistory)라는 prop을 주었으나, v6에서는 따로 historyimport해올 필요 없이 MemoryRouter로 감싼 컴포넌트에 initialEntries라는 prop으로 URL을 보내는 방식으로 변경되었습니다.

위와 같이 변경하고 npm run test 명령어를 통해 테스트를 수행하였더니 성공적인 결과를 확인할 수 있었습니다.

(추가)

MemoryRouter에 대한 부연 설명을 위해 좀더 검색을 해 보다가 좀더 쉽게 공통화할 수 있는 방법을 찾게 되었습니다.

import { Route, MemoryRouter, Routes } from "react-router-dom"

export const renderWithRouterMatch = (
  ui: JSX.Element,
  route: string | string[],
  path: string,
  children?: JSX.Element
) => {
  return (
    <MemoryRouter initialEntries={typeof route === "string" ? [route] : route}>
      {children && children}
      <Routes>
        <Route path={path} element={ui} />
      </Routes>
    </MemoryRouter>
  )
}

이제 테스트 코드에서 다음과 같이 사용할 수 있습니다.

it("renders component correctly", () => {
	const route = '/detail/0';
  const path = '/detail/:id';

	renderWithRouterMatch(<Detail />, route, path);
  ...
});

(참고 : https://woong-jae.com/react/220717-useParams-testing)


profile

공자윤 (SK플래닛)
글쓰기를 좋아하는 프론트엔드 개발자입니다.

Copyright © Jayoon Kong 2023, all right reserved.