60.7K Views
January 20, 24
スライド概要
BuriKaigi2024 https://burikaigi.dev/speakers/024/
Webフロントエンドエンジニア
#burikaigi_m Next.js App Routerを駆使したアプリケーション開発: React Server Componentを 使った機能実装とテストの結合 2024年1⽉20⽇ 株式会社エイチームライフデザイン デザイン開発本部 引越しプロダクト開発部 開発G ⻑尾准誠 © 2023 Ateam LifeDesign Inc. 1
#burikaigi_m ● ⾃⼰紹介 ながお じゅんせい ⻑尾准誠 株式会社エイチームライフデザイン デザイン開発本部引越しプロダクト開発部 各種サービス/junseinagao twitter.com/junpai_code 経歴 ● 2022年9⽉~ エイチームライフデザインに中途⼊社 引越し⾒積もり⽐較サービスに関連する新規サービス開発 反応⾒るのが好きなので、Xでセッションの感想をつど投げておいてくださると嬉しいです。 © 2023 Ateam LifeDesign Inc. 2
#burikaigi_m 予備知識 © 2023 Ateam LifeDesign Inc.
● 予備知識 #burikaigi_m React Server Component ● Next.js App Routerで使えるサーバーコンテキストで実⾏できる処理を記 述できるReactコンポーネント ● サーバーコンテキストで計算されたコンポーネントがクライアントに配信 されるので、Node.jsやサーバーコンテキストに依存した処理をコンポーネ ントと合わせて記述できる ● Next.js Pages RouterのgetServerSideProps, getStaticPropsのAPIの 表現を変えたモノとも⾔える © 2023 Ateam LifeDesign Inc.
● 予備知識 #burikaigi_m Route Handlers ● Next.js Pages RouterでAPI Routesと呼ばれているAPI ● Next.jsに柔軟に各種HTTPエンドポイントを作れる (クレジット:図は https://nextjs.org/docs/app/building-your-application/routing/route-handlers より 2024年01⽉19⽇アクセス © 2023 Ateam LifeDesign Inc. )
● 予備知識 #burikaigi_m Server Actions ● Next.jsでは、フォームからのリクエストを受けるエンドポイント(Web API) を作成するのを助けてくれるAPI ● formタグのaction属性に関数を渡す表現⽅法で実装できる ● クライアントコンポーネントと⼀緒にサーバーコンテキストの処理を扱える 注) 元々、Next.jsではなくreact@canaryの持つAPI © 2023 Ateam LifeDesign Inc.
#burikaigi_m アジェンダ © 2023 Ateam LifeDesign Inc.
● 本⽇のアジェンダ #burikaigi_m RSC,Server Actionsを採⽤したプロジェクトのテスト実装は どのように進めていけば良いのか...という話をします 1. React Server Component (RSC), Server Actionsを採⽤して 開発したプロダクトの事例‧構成の紹介 2. フロントエンドアプリケーションのテスト戦略 3. テスト実装の例とポイント 4. 質疑応答 © 2023 Ateam LifeDesign Inc.
#burikaigi_m 本⽇のメッセージ © 2023 Ateam LifeDesign Inc.
#burikaigi_m ● 本⽇のメッセージ Next.js App RouterでもStorybookを活⽤した コンポーネントテストを実施しよう! App Routerでもやれる! © 2023 Ateam LifeDesign Inc.
#burikaigi_m プロダクトの事例‧構成の紹介 © 2023 Ateam LifeDesign Inc.
● 開発したプロダクトの事例‧構成の紹介 #burikaigi_m 引越しに関するTodo管理アプリケーション (機能) 引越しするユーザーが⾃⾝に合わせて最適化された Todoを管理できるアプリ ● 簡単なフォームを提出すると専⽤のTodoリスト(マイ ページ)にアクセスできるようになる ● それぞれのタスクのチェック‧アンチェック ができる ● © 2023 Ateam LifeDesign Inc. 「編集」から引越し予定⽇を更新できる
● 開発したプロダクトの事例‧構成の紹介 引越しに関するTodo管理アプリケーション (実装) RSC (React Server Component)で タスク⼀覧をサーバーで取得して描画 © 2023 Ateam LifeDesign Inc. #burikaigi_m
● 開発したプロダクトの事例‧構成の紹介 #burikaigi_m 引越しに関するTodo管理アプリケーション (実装) Route Handler でタスクチェックAPIを作成 + router.refresh() で画⾯を更新 ( “next/navigation”のrouter.refresh()で クライアントのReact Stateを保ったまま、 RSCの再計算と配信ができる ) © 2023 Ateam LifeDesign Inc.
● 開発したプロダクトの事例‧構成の紹介 引越しに関するTodo管理アプリケーション (実装) Server Action で ⽇付変更APIを作成 © 2023 Ateam LifeDesign Inc. #burikaigi_m
● 開発したプロダクトの事例‧構成の紹介 引越しに関するTodo管理アプリケーション (実装) © 2023 Ateam LifeDesign Inc. ● RSCで タスク⼀覧を取得して描画 ● Route Handler で タスクチェックAPIを作成 + router.refresh() で更新 ● Server Action で⽇付変更 APIを作成 #burikaigi_m
#burikaigi_m ● 開発したプロダクトの事例‧構成の紹介 引越しに関するTodo管理アプリケーション (設計) 配信 ● ● AWS Fargate + CDN 同⼀VPC内でPostgresqlに接続 認証 ● NextAuthで作成したAPIで Cookieを⽤いたセッション管理を⾏う データ取得‧更新 ● ● © 2023 Ateam LifeDesign Inc. データ取得は、React Server Component データ更新は、Route HandlerかServer Actions を⽤いてサーバーコンテキストを⽤意して都度 Prismaで⾏う。
#burikaigi_m フロントエンドアプリケーションの テスト戦略 © 2023 Ateam LifeDesign Inc.
● フロントエンドアプリケーションのテスト戦略 このアプリケーションのテスト設計を考える ● E2Eテスト (Playwright) ● UIテスト、コンポーネントテスト (Storybook, Storybook play function, React Testing Library) ● DB接続を伴うロジックの結合テスト (Jest) ● カスタムフックス含むロジックの単体テスト (Jest, React Testing Library) © 2023 Ateam LifeDesign Inc. #burikaigi_m
#burikaigi_m ● フロントエンドアプリケーションのテスト戦略 このアプリケーションのテスト設計を考える ● E2Eテスト フロントエンドアプリケーション におけるテスト実装の鍵 (Playwright) ● UIテスト、コンポーネントテスト (Storybook, Storybook play function, React Testing Library) ● DB接続を伴うロジックの結合テスト (Jest) ● カスタムフックス含むロジックの単体テスト (Jest, React Testing Library) © 2023 Ateam LifeDesign Inc.
● フロントエンドアプリケーションのテスト戦略 #burikaigi_m コンポーネントテストが重要 ● E2EテストはすぐにCIが伸びる(20分~になりがち)‧偽陽性が多くなるので やたらと増やしたくない ● DB接続を伴うロジックの結合テスト‧カスタムフックス含むロジックの単 体テストだけでは、「このアプリケーションはテストがあるからリグレッ ションにすぐ気づけるぞ!」という温度感までカバーしきれない ● フロントエンドアプリケーションなのだからコンポーネントの表⽰‧動作 確認が全体のテストケースの体感半分ぐらいを占めてる(はず) © 2023 Ateam LifeDesign Inc.
● フロントエンドアプリケーションのテスト戦略 #burikaigi_m コンポーネントテストの実装⽅法 Storybookを使う、React Testing Libraryは使わない ● render(<></>)関数を使ってテストコード上でレンダリングするアレのこと ● React Testing Libraryで実装するコンポーネントテストは、 実装の都度たくさんのテストコードを書く‧更新する必要があって⼤変 Storybookで正しくレンダリング‧表⽰されることが充分なテストケー スたりえる ● 「どのようなテキストが表⽰されているか、スタイルが正しいか、クリックできる か、などビジュアル‧ブラウザのイベントに関連するテストケースは、 Storybookでストーリーを⽬で⾒て‧操作して確認できれば充分 © 2023 Ateam LifeDesign Inc.
● フロントエンドアプリケーションのテスト戦略 #burikaigi_m コンポーネントテストの実装⽅法 VRT(Visual Regression Test)とplay関数で⾃動テストにする ● Storybookのストーリー毎にVRT(Visual Regression Test)を実施していれば、 VRTを確認‧更新し続けていること⾃体がUIに関するテストケースを更新しているのと同じ意味 をもてる ● プロジェクトでは、Chromaticを使ってStorybookのVRTとplay functionのCIを実⾏している VRT(Visual Regression Test)を腐らせず続けることも重要 ● ● ● VRTもE2Eと同じく偽陽性が多い‧すぐにメンテされなくなってゆく いかにスナップショットのオリジナルの更新を仕組み化してめんどくさがらずに⾏えるか プロジェクトでは、デザイナーにVRTの確認をUIレビューという形で依頼している © 2023 Ateam LifeDesign Inc.
#burikaigi_m テスト実装の例とポイント © 2023 Ateam LifeDesign Inc.
#burikaigi_m テスト実装の例とポイント ~コンポーネントテスト~ © 2023 Ateam LifeDesign Inc.
#burikaigi_m ● テスト実装の例とポイント UIテスト、コンポーネントテスト UIコンポーネントだけでなく、 データ‧ロジックを含んだコンポーネン トをStorybookで表⽰するのがポイント ● ● ● (マイページのディレクトリ構造) © 2023 Ateam LifeDesign Inc. このディレクトリ構造にあるtsxファイルの Storyは全て作った⽅が良い なぜ?:これらがコンポーネントが機能実装、 リファクタリングなどにより⼀番変更が⼊る可 能性が⾼いから 変更が少ないコードだけをテストしていても、 テストの効能は得づらい
#burikaigi_m ● テスト実装の例とポイント UIテスト、コンポーネントテスト TodoListPage.tsx をStorybookで表⽰し て、「実際の挙動と同じくデータが表⽰ されている」ことを確認できれば それが、このページの結合テスト‧コン ポーネントテスト相当といえる (マイページのディレクトリ構造) © 2023 Ateam LifeDesign Inc.
#burikaigi_m ● テスト実装の例とポイント UIテスト、コンポーネントテスト (page.tsx) Async Componentはデータを取得して下層 に流し込むラッパー層にしてしまう ● Async Componentはpage.tsxのみ ● いわゆる、Presentaition / Container Pattern ● getTodoListPageData()はJestでテストするの で、呼び出すだけのpage.tsxをテストする意 味をほぼなくすことができる © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント #burikaigi_m UIテスト、コンポーネントテスト (TodoListPage.stories.tsx) © 2023 Ateam LifeDesign Inc.
#burikaigi_m ● テスト実装の例とポイント UIテスト、コンポーネントテスト (TodoListPage.stories.tsx) Storybook play functionを⽤いて Playwrightを(内部的に)使いながらテス トを⾏う ● router.refresh()の呼び出しを確認 しているのがポイント。データ更新され るところまでは確認できない(重要)が、 更新されるトリガーを発⽕しているとこ ろまではテストできる。 ● フロントがデータのレンダリングに集中 する設計のアプリなら充分 © 2023 Ateam LifeDesign Inc.
#burikaigi_m テスト実装の例とポイント ~DB接続を伴う(バックエンド部分の)結合テスト~ © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント DB接続を伴うロジックの結合テスト (RSC) © 2023 Ateam LifeDesign Inc. #burikaigi_m
● テスト実装の例とポイント #burikaigi_m DB接続を伴うロジックの結合テスト (RSC) ● データ取得‧整形する関数をテストすれば良いだけなので、 とくに実装が難しいポイントはない。Node.jsでテスト実装するのと同じ。 ● DBを伴うテストは、モックするか、実DB叩くか、トランザクションロールバックを 活⽤するか等、テスト環境を⽤意するにあたって少し考える必要がある ● このプロジェクトでは、実DBに接続しつつもデータを作成しないよう トランザクションロールバック(Quramy/jest-prisma)を使ってテストを実装 この⽅法では、 Jestの実⾏時間が10s~に伸びる問題がすぐに⽣まれるが、今回は深く掘り下げない © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント #burikaigi_m DB接続を伴うロジックの結合テスト (Route Handler) © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント #burikaigi_m DB接続を伴うロジックの結合テスト (Route Handler) ● こちらも、特に実装が特別ハマるポイントはない ● テストケースは、Web APIをテストするときと同じ観点で書けば良い ● 強いて⾔えばRoute Handlerの関数(GET, POST, PUT等)の呼び出し⽅に注意 VercelがData Access layerと紹介しているような外部とやりとりする機能をまとめたモ ジュールを作成しておいた⽅が、JestでもStorybookでもモックが簡単になるので採⽤し た⽅が良い © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント #burikaigi_m RSC, Route Handlerについては以上です © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント Server Actionsについては? © 2023 Ateam LifeDesign Inc. #burikaigi_m
#burikaigi_m ● テスト実装の例とポイント 実装された機能のおさらい Server Action で ⽇付変更APIを作成 © 2023 Ateam LifeDesign Inc. Server Actionで作成 した⽇付変更APIを フォームサブミット で発⽕させると、 データが更新される
● テスト実装の例とポイント #burikaigi_m 単体テスト、結合テスト (Server Actions) で Server Actionの関数を別ファイルに切り分けられるので、 ● テストしやすいように分ける © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント 単体テスト、結合テスト (Server Actions) ● FormDataがNode.jsにはないのでjsdomを使⽤すること ● 後は普通の関数と同じように実装できる © 2023 Ateam LifeDesign Inc. #burikaigi_m
● テスト実装の例とポイント #burikaigi_m UIテスト、コンポーネントテスト (Server Actions) ● ⿁⾨。Server ActionsをimportしたファイルはStorybookのビルドに含め られない (Server Actionsはimportできない) ● 「prisma(Node.jsに依存している)をimportしているファイルがある」ぐ らいだったら、storybook-addon-module-mockでstoriesで依存を持つ モジュールをモックしてやれば良い。 補⾜) 2024/1/19に調べたら「propsとしてserver actionsを渡せる」ことを利⽤した解決策もありそう FYI: https://github.com/storybookjs/storybook/issues/21540#issuecomment-1881895273 © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント #burikaigi_m UIテスト、コンポーネントテスト (Server Actions) ● プロジェクトでは、しかたないのでStorybookにレンダリングする⽤の モックコンポーネントを作った( formにaction要素を指定していない ) © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント #burikaigi_m Server Actionsでも難なく(?)テスト実装できた🎉 © 2023 Ateam LifeDesign Inc.
● 最後に #burikaigi_m データ‧ロジックを扱う中間層のコンポーネント含めて、Storybookに 登録できるかが実践的なフロントエンドテストの鍵だと思う ● ただし、Storybookはページをまたいだ動作の確認はニガテ(ほぼできない) ● SPA構成ならば、mswでエンドポイントをモックしてStorybook上で アプリケーションをほとんど再現できる。 ● MPAなサイト‧データ取得がサーバーの構成だと⼯夫が必要になってくる。 ● それを⾒越してカバー仕切れない範囲を他のテスト⽅法‧実装⼯夫で補う Async Componentを⼦に持つrender-as-you-fetchパターンのテスト実装は次回調査します © 2023 Ateam LifeDesign Inc.
#burikaigi_m ● 本⽇のメッセージ Next.js App RouterでもStorybookを活⽤した コンポーネントテストを実施しよう! App Routerでもやれる! Next.js App Routerを駆使したアプリケーション開発:React Server Componentを使った能実装とテストの結合 2024年01⽉20⽇ ⻑尾准誠 © 2023 Ateam LifeDesign Inc.
#burikaigi_m ご清聴ありがとうございました © 2023 Ateam LifeDesign Inc.
#burikaigi_m 質疑応答 © 2023 Ateam LifeDesign Inc.
#burikaigi_m フロントエンドアプリケーションの テスト戦略 (APPENDIX) © 2023 Ateam LifeDesign Inc.
● フロントエンドアプリケーションのテスト戦略 #burikaigi_m E2Eテスト ⽅針 ● ハッピーパスを中⼼に他のテストでは補えないテストはE2Eテストに回す。 ● 当然にCIが重くなるのでやたらと増やしたくはない テストケース例 ● ● 「チェックしたら、リストから取り除かれる」 (NextAuthを使った認証も、ここでカバー) © 2023 Ateam LifeDesign Inc.
● フロントエンドアプリケーションのテスト戦略 #burikaigi_m UIテスト、コンポーネントテスト ⽅針 ● React Testing Libraryで実装するコンポーネントテストは、 実装の都度たくさんテストコードを書く‧更新する必要があって⼤変だからしない (様は、DOM構造‧コンポーネントという変わりやすい要素に対してコスパが悪い) ● Storybook(コンポーネント単体でレンダリングするシステム)に登録しておくだけで、 正しくレンダリングされることが充分なテストケースになる ● Storybookのストーリー毎にVRT(Visual Regression Test)を実施していれば、 VRTを確認‧更新し続けていること⾃体がUIに関するテストケースを更新しているの と同じ意味をもてる テストケース例 ● ● 「未完了のタスクのみ表⽰する」(Storybookでレンダリング) 「チェックボックスをタップしてチェックできる」(Storybook play functionでイベント操作) © 2023 Ateam LifeDesign Inc.
● フロントエンドアプリケーションのテスト戦略 DB接続を伴うロジックの結合テスト ⽅針 ● 各APIエンドポイント‧ロジックに対して、 Web APIのテストを実装するのと同じようにテストを実装する テストケース例 ● ● 「引越し⽇を更新する」 「200のとき、取得したタスクの⼀覧を返す」 © 2023 Ateam LifeDesign Inc. #burikaigi_m
● フロントエンドアプリケーションのテスト戦略 カスタムフックス含むロジックの単体テスト ⽅針 ● サーバー、クライアント問わずロジックは関数に切り出して 単体テストに回す テストケース例 ● ● ● 「タスクを予定時期毎にグルーピングしたMapを返す」 「チェックされたらタスクチェックAPIを呼び出す」 「パラメーターを正しくバリデーションする」 © 2023 Ateam LifeDesign Inc. #burikaigi_m
#burikaigi_m テスト実装の例とポイント (APPENDIX) © 2023 Ateam LifeDesign Inc.
#burikaigi_m ● テスト実装の例とポイント 実装された機能のおさらい React Server Componentでタスク ⼀覧を取得して描画 Route Handler で作成したタスク チェックAPIを叩いて、 router.refresh() で更新 © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント #burikaigi_m E2Eテストの実装 ● ● テストケース:「マイページへアクセスできる」 認証を通ってページを表⽰させて終わり ● 「正しいデータで表⽰できるか」はUIテスト、DB接続を⽤いた結合テスト でカバーしているので実装しない (E2Eテスト実装は、世の中に深い説明をされている⽅がたくさんいるので 今回のセッションではあまり深く掘り下げない) © 2023 Ateam LifeDesign Inc.
● テスト実装の例とポイント #burikaigi_m カスタムフックス含むロジックの単体テスト ● 単体テストを書きやすくするために、コンポーネントが持ってるロジック はカスタムフックスに詰めた⽅が良い(もし、コンポーネントに直で書かれ ていたら⼤変な思いをしてコンポーネントテストを書くことになる) © 2023 Ateam LifeDesign Inc.