【React.js/Apollo】MockProviderを使ったGraphQL Queryを伴うコンポーネントテストの実装
概要
GraphQLリクエストを伴うコンポーネントテストについて試行錯誤した内容を記事に残したいと思います。
今回の記事では、Queryのテストについて説明します。Mutationのテストはスコープ外です。
今回のサンプルアプリケーション
今回説明するサンプルアプリケーションは以下のリポジトリで共有しております。
利用スタックは以下の通りです。
- ServerSide
- Ruby: 3.0
- Ruby on Rails: 6.1
- graphql-ruby
- Frontend
- TypeScript
- React.js
- apollo-client
- Frontend Testing
また、ServerSide(Ruby)とFrontend(React.js,TypeScript)のGraphQLスキーマ情報を共有するために、graphql-code-generatorを利用しています。詳細はこの記事では割愛します。興味がある方は以下の記事が参考になります。
テスト対象のページ
今回、テスト対象となるページは、以下の画像投稿一覧ページです。
GraphQLのQuery(usePostsQuery
)で取得した画像投稿をシンプルに一覧表示しています。
import * as React from 'react';
import { usePostsQuery } from '@/graphql/generated/graphql';
const PostListPage: React.VFC = () => {
const { loading, error, data } = usePostsQuery();
if (loading) return <div>Loading...</div>;
if (error || !data) return <div>Error</div>;
return (
<ul>
{data.posts.map((post) => (
<li key={`post-${post.id}`}>
<p>{`No.${post.id}: ${post.displayName}`}</p>
<img src={post.image} alt="post" />
</li>
))}
</ul>
);
};
export default PostListPage;
Github上のコードはこちらから確認できます
テストコード
今回のテスト対象のコードは、GraphQLによるAPIリクエストが行われます。この場合、GraphQLリクエストをMockすることが必要です。
往々にしてAPIリクエストのモックが大変なのですが、Apollo ClientではMockedProviderというMock処理が提供されています。これを利用することで、とても簡単にGraphQLリクエストをMockすることができます。
以下に、実際のテストコードを記載します。
import * as React from 'react';
import { act, render } from '@testing-library/react';
import { InMemoryCache } from '@apollo/client';
import { MockedProvider, MockedResponse } from '@apollo/client/testing';
import { PostsDocument } from '@/graphql/generated/graphql';
import PostListPage from '@/containers/PostListPage';
const mocks = [
{
request: {
query: PostsDocument,
},
result: {
data: {
posts: [
{
id: '1',
image: '/uploads/post/image/1/test1.png',
displayName: 'TEST_DISPLAY_NAME1',
createdAt: '2021-09-10T11:00:00Z',
updatedAt: '2021-09-10T11:00:00Z',
__typename: 'Post',
},
{
id: '2',
image: '/uploads/post/image/2/test2.png',
displayName: 'TEST_DISPLAY_NAME2',
createdAt: '2021-09-10T12:00:00Z',
updatedAt: '2021-09-10T12:00:00Z',
__typename: 'Post',
},
],
},
},
},
];
describe('Loading state', () => {
it('Snapshot test', () => {
const { asFragment } = render(
<MockedProvider
mocks={mocks}
defaultOptions={{
watchQuery: { fetchPolicy: 'no-cache' },
query: { fetchPolicy: 'no-cache' },
}}
cache={new InMemoryCache({ resultCaching: false })}
addTypename
>
<PostListPage />
</MockedProvider>
);
expect(asFragment()).toMatchSnapshot();
});
});
Github上のコードはこちらから確認できます
内容が多いので、それぞれ順を追って説明します。
MockedProviderの利用方法
まず、テスト対象のコンポーネントをrenderする箇所です。テスト対象のPostListPage
をrenderする際に、GraphQLリクエストをMockするためにMockedProvider
でWrapしています。
const { asFragment } = render(
<MockedProvider
mocks={mocks}
defaultOptions={{
watchQuery: { fetchPolicy: 'no-cache' },
query: { fetchPolicy: 'no-cache' },
}}
cache={new InMemoryCache({ resultCaching: false })}
addTypename
>
<PostListPage />
</MockedProvider>
);
MockedProvider
の各Propsにオプションを指定することができます。
mocks
には、MockしたいGraphQLリクエストの内容を記述します。request
には、Mock対象のQueryパラメタを指定し、result
にはQueryに対するレスポンスを指定します。
補足として、request.query
で指定しているPostsDocument
はgraphql-code-generator
で自動生成されるpostsQuery
のQuery内容です。
const mocks = [
{
request: {
query: PostsDocument,
/**
PostsDocument = gql`query posts {
posts {
id
image
displayName
createdAt
updatedAt
}
}`;
*/
},
result: {
data: {
posts: [
{
id: '1',
image: '/uploads/post/image/1/test1.png',
displayName: 'TEST_DISPLAY_NAME1',
createdAt: '2021-09-10T11:00:00Z',
updatedAt: '2021-09-10T11:00:00Z',
__typename: 'Post',
},
{
id: '2',
image: '/uploads/post/image/2/test2.png',
displayName: 'TEST_DISPLAY_NAME2',
createdAt: '2021-09-10T12:00:00Z',
updatedAt: '2021-09-10T12:00:00Z',
__typename: 'Post',
},
],
},
},
},
];
また、他のProps(cache
, defaultOptions
)ではキャッシュをオフにする設定を行なっています。テスト中にApollo Clientのキャッシュが残ってしまうとテストに影響があるので、キャッシュが残らないように設定しています。
GraphQLのリクエスト状態に応じたテスト
今回のテストでは、上述の設定でコンポーネントをrenderした結果をスナップショットテストを行なっています。
この状態でテストを行うとGraphQLクエリをMockできるのですが、GraphQLクエリの結果が画面表示される前にテストが終わってしまうと、Loading状態表示がスナップショットに残ります。
describe('Loading state', () => {
it('Snapshot test', () => {
const { asFragment } = render(
<MockedProvider
mocks={mocks}
defaultOptions={{
watchQuery: { fetchPolicy: 'no-cache' },
query: { fetchPolicy: 'no-cache' },
}}
cache={new InMemoryCache({ resultCaching: false })}
addTypename
>
<PostListPage />
</MockedProvider>
);
// GraphQLリクエスト中のため、Loading状態がスナップショットテストに残る
expect(asFragment()).toMatchSnapshot();
});
});
そのため、Loading完了後の結果のテストを行いたい場合は、少しSleep処理を追加すれば対応できます。今回は簡単なSleep処理の関数(sleep
)を実装して対応しました
const sleep = async (ms: number) => {
await new Promise((res) => setTimeout(res, ms));
};
describe('Loaded state', () => {
it('Snapshot test', async () => {
const { asFragment } = render(
<MockedProvider
mocks={mocks}
defaultOptions={{
watchQuery: { fetchPolicy: 'no-cache' },
query: { fetchPolicy: 'no-cache' },
}}
cache={new InMemoryCache({ resultCaching: false })}
addTypename
>
<PostListPage />
</MockedProvider>
);
// Loadingが完了するまで、少しSleepする
await act(async () => {
await sleep(10);
});
// Loading完了後の結果をスナップショットテストに残す
expect(asFragment()).toMatchSnapshot();
});
});
今回のスナップショットテストの結果は以下の通りです。
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PostListPage Loaded state Snapshot test 1`] = `
<DocumentFragment>
<ul>
<li>
<p>
No.1: TEST_DISPLAY_NAME1
</p>
<img
alt="post"
src="/uploads/post/image/1/test1.png"
/>
</li>
<li>
<p>
No.2: TEST_DISPLAY_NAME2
</p>
<img
alt="post"
src="/uploads/post/image/2/test2.png"
/>
</li>
</ul>
</DocumentFragment>
`;
exports[`PostListPage Loading state Snapshot test 1`] = `
<DocumentFragment>
<div>
Loading...
</div>
</DocumentFragment>
`;
参考文献
Author And Source
この問題について(【React.js/Apollo】MockProviderを使ったGraphQL Queryを伴うコンポーネントテストの実装), 我々は、より多くの情報をここで見つけました https://zenn.dev/ushinji/articles/b20b8e9cd8430d著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol