1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

MockedProviderでApollo Clientを使ったコンポーネントのテスト

Posted at

はじめに

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. レスポンスのモックを定義

requestresponse フィールドを持つオブジェクトの配列で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のご紹介でした。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?