Runner in the High

技術のことをかくこころみ

Next.jsアプリケーションのテスト方針覚書

現時点での自分の考えを雑なスナップショットとしてメモ

前提

  • ユニットテストに使うツールはjest(あるいはvitest)と@testing-library/reactを想定
  • テストに対応するモジュールを見つけやすいように __tests__ディレクトリは使わず、テスト対象と同じディレクトリに xxx.test.(ts|tsx) としてテストコードを配置する
  • 最低限のディレクトリ構成としてpages/components/hooksを用意し、必要に応じてlibなどのディレクトリを追加する
  • Next.jsなどフレームワークに対する依存を無理やり切り離そうとしない。
    • ほぼニコイチな存在なので逆に面倒なことになる。
    • テストしやすい(複雑なモックが必要ない)状態でメンテナブルなテストがたくさん書かれている方が重要。

Pages

  • hook/componentsを組み合わせて画面を実装する
    • データ取得や計算、変換などのロジックを直接ここには書かない
    • Next.jsに対する依存は可能な限りこのpageのみに限定する
  • ロジックがないことを前提にするので、pagesにはユニットテストを書かない
    • しかし本当に必要な場合のみ限定的にE2Eテストを書く
import { useRouter } from "next/router"
import { UsersView } from "@components/profile"

const Users: NextPage = () => {
  const router = useRouter()

  // onUserClickedはnext/routerに依存した実装
  const onUserClicked = useCallback((id) => {
    router.push(`/profile/${id}`)
  }, [router])

  return (
    <UsersView onUserClicked={onUserClicked} />
  )
}

Components

  • 画面を組み立てるためのコンポーネントを実装。原則ここにはすべてユニットテストを書く。
    • Atomicデザインはルールと実装の一貫性を維持するのが難しいため持ち込まない。
    • 状態遷移などのケースがないコンポーネントに対するテストの最小構成は@testing-library/reactのrender関数で実行時エラーなくレンダリングできるかことをチェックするだけでよい。
  • テスト時にimportモックが必要になるようなNext.js組み込みのモジュール(eg. next/navigation)には依存させない
    • 必要な場合にはコールバックなどで抽象的に依存させてpages側で依存を呼び出す関数を実装として注入する。
    • <Image><Link> などはモックしなくてよいので問題ない
import { useUsers } from "@hooks/user"

type Props = {
  // 利用するpageからrouter.pushによる遷移の実装の注入を期待したI/F
  onUserClicked: (id: string) => void
}

export const UsersView: React.FC<Props> = (props) => {
  const { users } = useUsers()

  return (
    <Container>
      {users.map((user) => (
         <UserContainer onClick={() => props.onUserClicked(user.id)}>
           <UserAvater user={user.image} />
           <UserName user={user.name} />
         </UserContainer>
       )}
    </Container>
  )
}

Hooks

  • データ取得や計算などコンポーネントから独立したpage/componentで必要なロジックを実装
    • 原則全てにユニットテストを書く。
    • 最小構成は renderHook によるhookの呼び出しで実行時エラーが出ないことのテスト。
  • componentと同様にNext.jsのモジュールには非依存の実装とする