はじめに
この記事では、React Router を利用したWebアプリの自動テスト実装方法について記載していきます。
前提
開発環境は以下の通りです。
- Windows11
- VSCode
- TypeScript 4.9.5
- React 18.2.0
- React Router 6.10.0
- Vite 4.1.0
- Vitest 0.28.5
- @testing-library/react 14.0.0
- jsdom 21.1.0
また、事前に以下の準備をしています。
- Vite でプロジェクトファイルを作成
- VitestでReact Testing Libraryを使えるように設定
※詳細はそれぞれ別の記事にまとめています。
デフォルトルートのテスト
Route
を利用したアプリ起動時のデフォルトのルートのテストをします。
アプリの実装
まず、App
コンポーネントを BrowserRouter
で囲み、ルーティングができるようにします。
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
次にルート("/"
)アクセス時に Home
コンポーネントを開くように実装します。
export const Home = () => {
return <div>You are home</div>;
};
import { Route, Routes } from "react-router-dom";
import { Home } from "./pages/Home";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
</Routes>
);
}
export default App;
実装できたので、動作確認をします。ローカルサーバーを立ち上げ、ブラウザで http://localhost:3000/ にアクセスすると、Homeページが表示されることを確認できました。
テストの実装
App
コンポーネントをレンダリングし、 「you are home」と表示されることをテストします(アプリ起動時に Home
コンポーネントが開くことをテストします)。
import { render, screen } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import { describe, test } from "vitest";
import App from "../../App";
describe("App routing", () => {
test("default root", () => {
render(<App />, { wrapper: BrowserRouter });
// verify page content for default route
expect(screen.getByText(/you are home/i)).toBeInTheDocument();
});
});
render
の第2引数の wrapper
は、第1引数のコンポーネント(コンテナ)を BrowserRouter
でラップするために追加しています。アプリ側のコードでは、App
を BrowserRouter
でラップしているので、テストコードでも同様にラップする必要があります。ラップしないと、エラーが発生します。
なお、第2引数ではなく、第1引数でラップすることも可能です。
render(
<BrowserRouter>
<App />
</BrowserRouter>
);
リンクのテスト
Link
を利用したページ上のリンクをクリックした際のページ遷移のテストをします。
アプリの実装
まず、遷移先のページである About
コンポーネントを作成し、ルーティングに追加します。
export const About = () => {
return <div>You are on the about page</div>;
};
import { Route, Routes } from "react-router-dom";
import { About } from "./pages/About";
import { Home } from "./pages/Home";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
}
export default App;
Home
ページ上で、リンクをクリックしたら、About
ページに遷移するようにします。
import { Link } from "react-router-dom";
export const Home = () => {
return (
<div>
<div>You are home</div>
<Link to="/about">About</Link>
</div>
);
};
テストの実装
App
コンポーネントをレンダリングした後、リンクをクリックし、その後、ページ上に「you are on the about page」と表示されることをテストします。
test("link", async () => {
render(<App />, { wrapper: BrowserRouter });
const user = userEvent.setup();
// verify page content for expected route after navigating
await user.click(screen.getByRole("link"));
expect(screen.getByText(/you are on the about page/i)).toBeInTheDocument();
});
なお、ページ上のリンクが1つだけの場合、上記の書き方でも問題ありませんが、以下のように複数ある場合、エラーになります。
import { Link } from "react-router-dom";
export const Home = () => {
return (
<div>
<div>You are home</div>
<Link to="/about">About</Link>
<br />
<Link to="/form">Form</Link>
</div>
);
};
解決方法は複数ありますが、例えば、第2引数に name
を追加することで、クリック対象のリンク(About
)を特定させることができます。
test("navigate from root to about by clicking link", async () => {
render(<App />, { wrapper: BrowserRouter });
const user = userEvent.setup();
// verify page content for expected route after navigating
await user.click(screen.getByRole("link"), { name: "About" });
expect(screen.getByText(/you are on the about page/i)).toBeInTheDocument();
});
リダイレクトのテスト
Navigate
を利用したリダイレクトのテストをします。
アプリの実装
ルート("/"
)アクセス時に "/home"
へリダイレクトするようにします。
import { Navigate, Route, Routes } from "react-router-dom";
import { About } from "./pages/About";
import { Home } from "./pages/Home";
function App() {
return (
<Routes>
<Route path="/" element={<Navigate to="/home" />} />
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
}
export default App;
テストの実装
App
コンポーネントをレンダリングした後、パスが "/home"
へ変わっていることを確認します。
まず、テストでパスを確認するため、パス確認用のコンポーネントを作成します。
import { useLocation } from "react-router-dom";
export const LOCATION_DISPLAY = "location-display";
export const LocationDisplay = () => {
const location = useLocation();
return <div data-testid={LOCATION_DISPLAY}>{location.pathname}</div>;
};
次にパス確認用のコンポーネントをテストコード上にレンダリングし、そのコンポーネントのパスが "/home"
であることをテストします。
test("default root", () => {
render(
<>
<App />
<LocationDisplay />
</>,
{ wrapper: BrowserRouter }
);
// verify page content for default route
expect(screen.getByText(/you are home/i)).toBeInTheDocument();
// verify page path redirected from root to home
expect(screen.getByTestId(LOCATION_DISPLAY)).toHaveTextContent("/home");
});
最後に
今回は React Router の Route
、Link
、Navigate
を利用したWebアプリの自動テストを実装しました。React Router にはまだまだたくさんの機能があるので、今後も自動テストを実装できるか確認していければと思っています。