🍙

Next.js + react-query + react-testing-libraryでモックサーバーからデータを取得するテストを書く

2022/02/09に公開

概要

本記事ではSSG + prefetch + Client Side Fetching(react-query)におけるテストを紹介します。
SEOを意識したい、かつリアルタイムで情報を更新したいようなサービスだと上記のようなレンダリング方法が採用されるかと思います。

実行環境

・Next 11.1.2
・react-query 3.34.12
・@testing-library/react 12.1.2
・jest 27.4.7

テストしたいページ

SSG + prefetch + CSFのページを用意します。
ビルド時に呼ばれるgetStaticPropsでAPIを叩き、レスポンスをpropsに渡しています。

その後クライアントでreact-queryによるfetchが走りますが、ここではinitialDataとして先程のpropsで渡されたデータを入れているのでそちらがpostsDataとしてレンダリングされます。

refetchOnMount: true のオプションを追加しているので、レンダリング後再度react-queryのfetchが走り、postsDataが上書きされるという流れになります。

posts-page.tsx
interface STATICPROPS {
  posts: POST[]
}

const PostsPage: React.FC<STATICPROPS> = ({ posts }) => {
  const { data: postsData, isError, isLoading } = useQuery<POST[], Error>('PostsList', getPostsList, {
    initialData: posts,
    refetchOnMount: true,
  });

  return (
    <ul>{postsData && postsData.map((post) => <Post key={post.id} {...post} />)}</ul>
  )
}
export default PostsPage

export const getStaticProps: GetStaticProps = async () => {
  const posts = await getAllPostsData()
  return {
    props: { posts },
  }
}
fetch.ts
export const getAllPostsData = async () => {
  const res = await fetch(
    new URL('https://myapi.com/posts/?offset=0&limit=50')
  )
  const posts = await res.json()
  return posts
}
postsList.ts
export const getPostsList = async () => {
  const res = await api
    .get("/posts", {
      params: {
        offset: 0,
        limit: 50,
      },
    })
    .catch((err) => {
      return err;
    });

  return res.data;
};

fetch.tspostsList.ts は同様のAPIを叩く処理になりますが、axiosをサーバーサイドで実行しようとするとエラーになるためfetch関数で分けて定義する必要があります。

テストコード

mockサーバーを立てる

テストでfetch処理を書くにはmockサーバーを立てて擬似的なレスポンスを用意する必要があります。

PostsPage.test.tsx
const handlers = [
  rest.get(
    'https://myapi.com/posts/',
    (req, res, ctx) => {
      const query = req.url.searchParams
      const offset = query.get("offset")
      const limit = query.get("limit")
      if (limit === '50') {
        return res(
          ctx.status(200),
          ctx.json([
            {
              id: 1,
              title: 'title1',
              description: 'description1',
              userId: 1,
              createdAt: new Date(),
              updatedAt: new Date(),
            }
          ])
        )
      }
    }
  )
]

const server = setupServer(...handlers);
beforeAll(() => {
  server.listen();
});
afterEach(() => {
  server.resetHandlers();
  cleanup();
});
afterAll(() => {
  server.close();
});

SSG + prefetchのテスト

初回レンダリングで先程定義したjsonデータが返っているかどうか確認します。

PostsPage.test.tsx
describe('Posts Page Static', () => {
  it('Pre-fetched by getStaticProps', async () => {
    const { page } = await getPage({
      route: '/',
    });
    render(page);
    expect(await screen.findByText('title1')).toBeInTheDocument()
  })
})

mockで定義したtitleの値が画面に描画されていることが確認できます。
なお getPagenext-page-tester のライブラリの関数です。

CSFのテスト

今回はprefetchで取得したデータがpropsとして渡されinitialDataとして表示されますので、そちらのデータをstaticPropsとして定義します。
その後CSFが走りデータが上書きされることを確認したいので、先程のmockサーバーで定義したものとは別の値を返すようにします。

PostsPage.test.tsx
describe('Home Page / useQuery', () => {
  const queryClient = new QueryClient()
  let staticProps: Posts[] = [
    {
      id: 2,
      title: 'title2',
      description: 'description2',
      userId: 2,
      createdAt: new Date(),
      updatedAt: new Date(),
    }
  ];
...

下記のテストでstaticPropsのデータが表示後にCSFでmockサーバーで定義した擬似データが返っていることが確認できます。
テストページのコンポーネントを QueryClientProvider でラップすることを忘れないようにします。

PostsPage.test.tsx
it('CSF', async () => {
  render(
    <QueryClientProvider client={queryClient}>
      <PostsPage posts={staticProps} />
    </QueryClientProvider>
  )

  expect(await screen.findByText('title2')).toBeInTheDocument(); //<- staticProps(SSG)
  expect(await screen.findByText('title1')).toBeInTheDocument(); //<- CSF
});

おわり

重い腰を上げてフロントエンドのテストを書き始めたので知見をまとめていこうと思います。
だれかの参考になったら幸いです
次はCookie周りのテストも書けたらいいな、、

GitHubで編集を提案

Discussion