現時点での自分の考えを雑なスナップショットとしてメモ
前提
- ユニットテストに使うツールは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
- 画面を組み立てるためのコンポーネントを実装。原則ここにはすべてユニットテストを書く。
- テスト時に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> ) }