はじめに
GraphQLの通信部分にApollo Clientを使っている場合、ApolloProvider
の代わりにApolloの提供しているMockedProvider
を使うことで、GraphQLサーバーに通信をすることなくReactコンポーネントのテストが可能です。
コンポーネントの例
IDを指定してユーザーを取得し、簡単なテキストを表示するコンポーネントです。
import React from 'react';
import { gql, useQuery } from '@apollo/client';
export const GET_USER_QUERY = gql`
query GetUser($id: ID) {
user(id: $id) {
id
name
age
}
}
`;
export function User({ id }) {
const { loading, error, data } = useQuery(
GET_USER_QUERY,
{ variables: { id } }
);
if (loading) return <p>Loading...</p>;
if (error) return <p>An error occurred.</p>;
return (
<p>
{data.user.name} is {data.user.age} years old.
</p>
);
}
それではテストを追加していきます。
1. レスポンスのモックを定義
request
と response
フィールドを持つオブジェクトの配列でoperationごとのレスポンスを定義します。
const mocks = [
{
request: {
query: GET_USER_QUERY,
variables: {
id: 1,
},
},
result: {
data: {
user: { id: 1, name: 'Tom', age: 25 },
},
},
},
];
関数でオブジェクトを返却することも可能
result: () => {
// ...何かしらのロジック...
return {
data: {
user: { id: 1, name: 'Tom', age: 25 },
},
}
},
2. 初期のloading状態のテスト
イベントループを待つことなく、すぐにそのままテストを実行すると初期のローディング状態でコンポーネントがレンダリングされます。
import TestRenderer from 'react-test-renderer';
import { MockedProvider } from '@apollo/client/testing';
...
it('renders without error', () => {
const component = TestRenderer.create(
<MockedProvider mocks={mocks} addTypename={false}>
<User id={1} />
</MockedProvider>,
);
const tree = component.toJSON();
expect(tree.children).toContain('Loading...');
});
リクエスト時、本来は__typename
フィールドがクエリに含まれるのですが、テストの際はaddTypename
をfalseにすることでモックのリクエストとレスポンスに__typename
フィールドを含める必要がなくなります。
3. success状態のテスト
setTimeoutしてMockedProviderがモックのレスポンスを取得する処理を走らせます
it('should render user', async () => {
const component = TestRenderer.create(
<MockedProvider mocks={mocks} addTypename={false}>
<User id={1} />
</MockedProvider>,
);
// レスポンスを取得
await new Promise(resolve => setTimeout(resolve, 0));
const p = component.root.findByType('p');
expect(p.children.join('')).toContain('Tom is 25 years old.');
});
4. error状態のテスト
モックにエラーのレスポンスを定義
const mocks = [
{
request: {
query: GET_USER_QUERY,
variables: {
id: 1,
},
},
error: new Error('An error occurred.'),
},
];
successの時と同様にモックのレスポンスを取得
it('should render error message', async () => {
const component = TestRenderer.create(
<MockedProvider mocks={mocks} addTypename={false}>
<User id={1} />
</MockedProvider>,
);
// レスポンスを取得
await new Promise(resolve => setTimeout(resolve, 0));
const tree = component.toJSON();
expect(tree.children).toContain('An error occurred');
});
Mutationのテスト
ユーザーを削除するボタンのコンポーネントです。
export const DELETE_USER_MUTATION = gql`
mutation deleteUser($id: ID!) {
deleteUser(id: $id) {
id
name
age
}
}
`;
export function DeleteButton() {
const [mutate, { loading, error, data }] = useMutation(DELETE_USER_MUTATION);
if (loading) return <p>Loading...</p>;
if (error) return <p>An error occurred.</p>;
if (data) return <p>Deleted.</p>;
return (
<button onClick={() => mutate({ variables: { id: 1 } })}>
Click to Delete Tom
</button>
);
}
まずはクエリの際と同様にモックのレスポンスを定義
const mocks = [
{
request: {
query: DELETE_USER_MUTATION,
variables: {
id: 1,
},
},
result: {
data: {
deleteUser: { id: 1, name: 'Tom', age: 25 },
},
},
},
];
onClick
をトリガーしてMutationを実行
it('should delete user', async () => {
const component = TestRenderer.create(
<MockedProvider mocks={mocks} addTypename={false}>
<DeleteButton />
</MockedProvider>,
);
// ボタンをクリック
const button = component.root.findByType('button');
button.props.onClick();
// レスポンスを取得
await new Promise(resolve => setTimeout(resolve, 0));
const tree = component.toJSON();
expect(tree.children).toContain('Deleted!');
});
まとめ
今回はGraphQLサーバーに通信をすることなくReactコンポーネントのテストができるMockedProviderのご紹介でした。