😎

apollo graphqlアプリのUIテスト

2021/07/13に公開

apollo graphqlを使ったUIテストのセットアップをしてみました。
備忘録で残します。
テストはreact-tesing-liblaryで行います。

以下、テスト対象のアプリです。
https://github.com/kupuma-ru21/apollo_relay_style_cursor_pagination
(以前、書いた記事にテスト対象のアプリの作成方法を記載してます。読まなくてもできるように進めたいと思います。)
https://zenn.dev/kupuma_ru21/articles/f56148ffcac7b0


前提

まず、普通にreact-tesing-liblaryを使ってみます。

src/test/App.test.tsx
import { render } from '@testing-library/react';
import App from '../App';

describe('App', () => {
  test('App test', async () => {
    const { findByTestId } = render(<App />);
    const targetTestId = await findByTestId('hoge');
    expect(targetTestId).not.toBe(null);
  });
});
src/App.tsx
// 省略
const App = () => {
// 省略(apollo/graphql使ってデータのフェッチなどしてます。)
  return (
    <div data-testid="hoge">
      <ul>
        {repositoriesInfo?.map((repository: any) => {
          const { name } = repository.node;
          return <li key={name}>{name}</li>;
        })}
      </ul>
      {/* 省略 */}
    </div>
  );
};

export default App;

でテストを実行すると、以下のようなerrorが表示されます。

Error: Uncaught [Invariant Violation: Could not find "client" in the context or passed in as an option. Wrap the root component in an <ApolloProvider>, or pass an ApolloClient instance in via options.]

「Providerでラップするか、ApolloClientインスタンスを渡してください」といった旨の内容です。

なので以下、docの通りテストのrenderを<MockedProvider />でラップします。
https://www.apollographql.com/docs/react/development-testing/testing/


  1. <MockedProvider />でラップ

今回はまず<MockedProvider />を使用しwrapper関数を作成しようと思います。

src/test/wrapMockedProvider.tsx
import { render } from '@testing-library/react';
import { MockedProvider, MockedResponse } from '@apollo/client/testing';

type Mocks = ReadonlyArray<MockedResponse>;
type WrapMockedProviderArg = { ui: React.ReactElement; mocks?: Mocks };

export const wrapMockedProvider = (
  wrapMockedProviderArg: WrapMockedProviderArg
) => {
  const { mocks, ui } = wrapMockedProviderArg;
  return render(
    <MockedProvider mocks={mocks} addTypename={false}>
      {ui}
    </MockedProvider>
  );
};

wrapper関数の内容としては、引数を2つ受け取ります。
引数はテスト対象のコンポーネント、テスト対象のコンポーネントで使用するmockを定義してます。


  1. App.test.tsxでwrapper関数(wrapMockedProvider)を使用する
src/test/App.test.tsx
import App from '../App';
import { wrapMockedProvider } from './wrapMockedProvider';

const testRender = () => wrapMockedProvider({ ui: <App /> });

describe('App', () => {
  test('App test', async () => {
    const { findByTestId } = testRender();
    const targetTestId = await findByTestId('hoge');
    expect(targetTestId).not.toBe(null);
  });
});

ここでテストを実行すると、「Providerでラップするか、ApolloClientインスタンスを渡してください」といった旨のエラーはなくなります。
ですが、テスト自体は失敗します。

原因はwrapMockedProviderの第2引数にmockを渡しておらず、
apolloでハンドリングしてるerror時の画面がレンダリングされるからです。(以下の部分)

src/App.tsx
const App = () => {
  const { error, ... } = useRepositoriesQuery({...});
  // 省略
  if (error) return <p>Error :(</p>;
  // 省略
export default App;

なのでdata-testId="hoge"を取得できずに失敗します。


  1. 正常にUIのテストを行えるようにする

App.test.tsxにmockを定義します。

App.test.tsx
import { RepositoriesDocument } from '../graphql/generate';
const mock = {
  request: {
    query: RepositoriesDocument,
    variables: {
      after: null,
      before: null,
      first: 5,
      last: null,
      query: 'apollo graphql test',
    },
  },
  result: {
    data: {
      search: {
        edges: [
          {
            cursor: 'Y3Vyc29yOjE=',
            node: { name: 'apollo-storybook-decorator' },
          },
        ],
        pageInfo: {
          endCursor: 'Y3Vyc29yOjU=',
          hasNextPage: true,
          hasPreviousPage: false,
          startCursor: 'Y3Vyc29yOjE=',
        },
        repositoryCount: 411,
      },
    },
  },
};

const testRender = () => wrapMockedProvider({ ui: <App />, mocks: [mock] });

行ったこととしては、App.tsxuseRepositoriesQueryを使用してるので、useRepositoriesQueryvariablesdataを擬似的に定義したmockをwrapMockedProviderに渡すことでデータフェッチ成功時のUIをテストできるようにしてます。

ここでテストを実行すると、無事テストが通ると思います。


こんな感じでapollo graphqlを使ったUIテストができます。
以下、テスト込みのリポジトリです。
https://github.com/kupuma-ru21/apollo-graphql-react-testing-app

おわり。

Discussion