Movatterモバイル変換


[0]ホーム

URL:


Zenn
TakepepeTakepepe
🎪

Testing Next.js - getServerSideProps & API Routes -

に公開
2022/05/14

Next.js の getServerSideProps & API Routes テスト手法についてまとめました。getServerSidePorps & API Routes に関するテストは「Cypress・Playwright」を利用することが多いと思いますが、本稿は Jest 単体テストの紹介です。以下はテストに使用するエコシステム一式です(Jest 等は略)

1.pageExtensions を設定する

本題に入る前に、pageExtensionsnext.config.jsに設定します。pageExtensions はpagesに含まれるファイルのうち、指定の拡張子をもつファイルが「Page・API 実装ファイルである」ことを指定するものです。

module.exports={pageExtensions:["page.tsx","api.ts"],};

この設定で、テストファイルをpagesに配備することが可能になります。実装ファイルのそばにテストファイルを置くことで、テストが書かれているかが一目瞭然になります。それぞれ同名の test.tsxtest.tsが対応するテストファイルです。

機能実装ファイルテストファイル
getServerSidePropspages/example.page.tsxpages/example.test.tsx
API Routespages/api/example.api.tspages/api/example.test.ts

2.MSW Handler factory 関数

モックにはMSW を利用します。ハンドラー関数は一度セットしてお終いではなく、テストケースによってレスポンスを変更したい事が多いです。そこで、以下の様な「ハンドラー関数を作るファクトリー関数」を用意しておくと、テストケースごとに API 詳細を意識する必要がなくなります。例えば以下のファクトリー関数は、200 | 400 のいずれかのレスポンスを返すハンドラーを作れます。

exportconstcreateHandler=(status:200|400=200)=>  rest.post<Data,{ id:string}, Data| Err>(path(),(req, res, ctx)=>{if(status===400||!req.body.title)returnres(        ctx.status(400),        ctx.json({ message:"Bad Request", status:400}));returnres(ctx.json(req.body));});

ハンドラーをインターセプトする際はserver.useを使用しますが、簡潔になることが見てとれます。ここでは単純なファクトリー関数としていますが、I/O はいくらでも工夫の余地があります。

test("400",async()=>{// Intercept mock Error  server.use(createHandler(400));// some test case});

余談ですが、このハンドラーファクトリー関数をデータ取得関数(fetcher)とセットで定義しておくと、スコープを特定しやすく・資材をまとめやすくなります。

src/fetcher├── posts│   ├── create│   │   ├── index.ts│   │   └── mock.ts│   ├── delete│   │   ├── index.ts│   │   └── mock.ts│   ├── list│   │   ├── index.ts│   │   └── mock.ts│   ├── show│   │   ├── index.ts│   │   └── mock.ts│   └── update│       ├── index.ts│       └── mock.ts└── type.ts

mock.tsにハンドラーファクトリー関数が含まれます。関連する型定義などを同包しても良いでしょう。aspida など、データ取得 client を生成している場合も同様です。

3.getServerSideProps のテスト手法

いよいよテストケースの作成です。プロジェクトで定義している MSW ハンドラーをセット、これがデフォルトの API モックとなります。

const server=setupMockServer(...handlers);

正常系の表示テスト

getServerSideProps関数内部に実装されている一連のデータ取得は、デフォルトの API モックレスポンスを得た状態になります。その結果を、<Page />コンポーネントに展開し、期待する正常系コンポーネント表示に至ったかを検証します。

test("If the data acquisition is successful, the title will be displayed.",async()=>{const res=awaitgetServerSideProps(gsspCtx());assertHasProps(res);render(<Page{...res.props}/>);expect(screen.getByText("Posts")).toBeInTheDocument();});

異常系の表示テスト

正常系のデフォルト API モックをserver.useで異常系に上書きします。以下の例では、データ取得に失敗(500 エラー)するものとしています。期待する異常系コンポーネント表示に至ったかを検証します。

test("If data acquisition fails, an error will be displayed",async()=>{  server.use(postListHandler(500));// Intercept mock Errorconst res=awaitgetServerSideProps(gsspCtx());assertHasProps(res);render(<Page{...res.props}/>);expect(screen.getByText("Internal Server Error")).toBeInTheDocument();});

Dynamic route のテスト

以下のようにgsspCtx({ query: { id: "lorem-ipsum" } })とすることで、getServerSideProps関数が参照する query param や path param を模すことができます。パラメーターに応じてコンポーネントを出し分けるページでは、パラメーター名称変更に伴うリグレッションを防げるでしょう。

test("If the data acquisition is successful, the title will be displayed.",async()=>{const res=awaitgetServerSideProps(gsspCtx({ query:{ id:"lorem-ipsum"}}));assertHasProps(res);render(<Page{...res.props}/>);expect(screen.getByText("Post: Lorem ipsum")).toBeInTheDocument();});

gsspCtx 関数内訳

先の例で使用していたgsspCtx()関数は、getServerSideProps関数の引数ctx作成関数です。node-mocks-httpcreateRequestcreateResponseを適用しつつ、query など値を注入できるようにしていました。

exportconst gsspCtx=(  ctx?: Partial<GetServerSidePropsContext>): GetServerSidePropsContext=>({  req:createRequest(),  res:createResponse(),  params:undefined,  query:{},  resolvedUrl:"",...ctx,});

assertHasProps 関数内訳

res をアサートしてるのはgetServerSideProps関数戻り値にpropsが含まれないことがあるからです。Assertion Functions として定義したassertHasProps関数を通過すると「props が存在する」と判定されます。

classAssertionErrorextendsError{}exportfunctionassertHasProps<T>(  res: GetServerSidePropsResult<T>):asserts resis{ props:T}{const hasProps=typeof res==="object"&&(resasany)["props"]&&typeof(resasany).props==="object";if(!hasProps)thrownewAssertionError("no props");}

4.API Routes のテスト手法

API Routes のテストはnext-test-api-route-handler を使うと簡単に行えます。testApiHandler関数のパラメータに、API Routes 定義であるhandlerを渡します。(MSW のハンドラーではありません)特に凝ったことはしていないので、詳細は next-test-api-route-handler の README をご参考ください。

test("201",async()=>{awaittestApiHandler({    handler,    url:"/api/posts",test:async({ fetch})=>{const res=awaitfetch(requestInit);awaitexpect(res.json()).resolves.toStrictEqual(body);},});});

MSW ハンドラーをインターセプトする要領はgetServerSideProps関数と同じです。server.use(postCreateHandler(400));で 400 レスポンスを再現します。

test("400",async()=>{// Intercept mock Error  server.use(postCreateHandler(400));awaittestApiHandler({    handler,    url:"/api/posts",test:async({ fetch})=>{const res=awaitfetch(requestInit);awaitexpect(res.json()).resolves.toStrictEqual({        message:"Bad Request",});},});});

まとめ

Cypress・Playwright のテストケース全てを代替するとまではいきませんが、多くのケースがカバー出来るのではないでしょうか。本稿サンプルコードは以下に公開しています。

https://github.com/takefumi-yoshii/testing-nextjs-gssp

Takepepe

Web App Developer.

Discussion


[8]ページ先頭

©2009-2025 Movatter.jp