💯

Tanstack Routerを絡めたコンポーネントのUnitテストの覚え書き

2025/02/14に公開

概要

コンポーネントのUnitテストを行う際に、Tanstack Routerが絡んでくるコンポーネントがあります。
いくつかハマりどころがあったのでその際の対応をメモしました。

前提

  • テストはJestで行う
    • "@testing-library/jest-dom": "^6.6.3"
    • "jest": "^29.7.0"
    • "jest-environment-jsdom": "^29.7.0"
    • "ts-jest": "^29.2.5"
  • ルーティングはTanstack Routerを用い、一部のコンポーネントでLinkやhooksを使っている
    • "@tanstack/react-router": "^1.77.5"

RouterProviderでのラップ

Tanstack RouterはRouterProviderでラップされていることが前提の挙動になります。
なので、Unitテスト時もその点を考慮する必要があります。
先行事例として以下がヒットしたので、そのまま流用します。

https://zenn.dev/bmth/scraps/329975ee962ec2

// src/__test__/DummyRouter.tsx
import {
  Outlet,
  RouterProvider,
  createMemoryHistory,
  createRootRoute,
  createRoute,
  createRouter,
} from "@tanstack/react-router";
import { type FC, type ReactNode } from "react";

export const DummyRouter: FC<{ component: () => ReactNode }> = ({
  component,
}) => {
  const rootRoute = createRootRoute({
    component: Outlet,
  });

  const indexRoute = createRoute({
    getParentRoute: () => rootRoute,
    path: "/",
    component,
  });

  const routeTree = rootRoute.addChildren([indexRoute]);
  const history = createMemoryHistory({ initialEntries: ["/"] });
  const router = createRouter({ routeTree, history });
  return <RouterProvider router={router} />;
};

さらに今回の環境ではMantineを使っていたので、そちらのProviderも複合しました。
他にもProviderが要求されるようなライブラリを使っている場合も同じような対応になるかと思います。

// src/__test__/Wrapper.tsx
import { MantineProvider } from "@mantine/core";
import { DummyRouter } from "./DummyRouter";

const Wrapper: React.JSXElementConstructor<{
  children: React.ReactNode;
}> = ({ children }) => {
  return (
    <DummyRouter
      component={() => <MantineProvider>{children}</MantineProvider>}
    />
  );
};

export default Wrapper;

これで各Unitテストを行う際にrenderの第二引数にこのWrapperを渡すことで、テスト対象のコンポーネントが必要なProviderでラップされている状況を作ります。

// src/__test__/Hoge.test.tsx
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
// "@/"でエイリアスを通している前提。ここは各環境に合わせて適宜置き換える
import Hoge from "@/components/Hoge";
import Wrapper from "@/__test__/Wrapper";

describe("Hoge Unit Test", () => {
  test("example", () => {
    // Wrapperでラップした状態でテスト
    render(<Hoge />, { wrapper: Wrapper });
    expect(screen.getByText("hogehoge")).toBeInTheDocument();
  });
});

Error: Not implemented: window.scrollToのエラー

「前提」のセクションで記載したバージョンでは、ここまでの対応だとUnitテストは実行されますが、最中にエラーが発生していました。
※エラーは発生するが、Unitテストの成否には影響がない状態

Error: Not implemented: window.scrollTo

どうやらTanstack Routerの内部でwindow.scrollToを使用しているようですが、Unitテスト環境下では使用できないためエラーになっているようです。

Tanstack Router側のIssueがあり、そこで解決策が明示されていました。

解決先は下記の記事にありました。
https://qiita.com/akameco/items/0edfdae02507204b24c8

これをそのまま流用します。

// <root>/testSetup.ts
// jest実行時にTanstack Router内でwindow.scrollToを要求されるが、unitテストには無関係なのでnoop化する
const noop = () => {};
Object.defineProperty(window, "scrollTo", { value: noop, writable: true });
jest.config.ts
+ "setupFiles": ["./testSetup.ts"]

これでエラーが発生しなくなりました。

まとめ

今回はTanstack Routerを用いた環境下でのコンポーネントのUnitテスト時のハマりどころをまとめました。
ルーティングのライブラリの中だとかなりメジャーな方なので、テスト時には皆さん同じような箇所でつまづいているのではないかと思います。
今回の記事が役立ちましたら幸いです。

Discussion