LoginSignup
1
0

More than 1 year has passed since last update.

Next.js + サーバーサイドTypeScript + 関数フレーバーでクリーンなアプリを作ったので実装意図とか書く Advent Calendar 2022
19日目株式会社mofmofに生息しているshwldです。

前日はChakraUIとReact Hook FormとZodとについて書きました

Vitestでテストする

モノレポの中で、複数パッケージを扱っており、パッケージによってテストツールも多少の違いがあります。

パッケージ 利用しているテストツール
apps/web (Next.js) Jest (Next.jsが最初からセットアップ済の next/jest
apps/worker (バックグラウンドワーカー)) なし
domain/core (ドメインモデル) Vitest
infrastructures/* (インフラ系) なし
use-cases/graphql-resolvers (GraphQLサーバーの実装) Vitest

基本的にはVitestを使っており、Next.jsだけデフォルトでテストの設定があるのでそれを使っています。

テストファイルの場所

テスト用のディレクトリを切ってまとめるか、コンポーネントや関数ファイルの隣に置くかがあると思いますが、後者を選択しています。

- src/models/account/mutations/build-account.ts
- src/models/account/mutations/build-account.test.ts

こちらのメリットは単純で、処理とテストをセットで直しやすいというところですが、クリーンなアーキテクチャを目指す上で、テストの依存が同じパッケージに入り込むというデメリットがあります。
まあでもそれよりも直しやすいほうが大事かなと言う判断。

インフラのテストがない

これは優先度が下がっていて実装してないです。
例えばRepositoryの実装だけをテストするのは必要だと思いますが、use-caseなどと結合した際のテストのほうが重要だと思っています。
use-caseのテストでDBをモックするか実DBを使うかみたいな話。
モックはせず実DBを使ってテストをする方針としており、Repository自体のテストは端折っています。
mailerなどはモックしてテストしているのでやった方がいいが、コード自体にに複雑さはないので、まだテストはないです。

コンポーネントのテスト

コンポーネントはNext.jsのコンポーネント構造で書きましたが、コンテナーとプレゼンテーションをわけていないので、基本的にGraphQL APIへの依存がコンポーネントに入り込んでいることが多いです。
なので、モックしてテストしているのですが、ここはGraphQLサーバー側の設計で、Queryのルートにviewerというフィールドが生えており、そいつの配下に扱えるオブジェクトがまとまっています。

/use-cases/graphql-resolvers/src/modules/viewer/object-resolvers/viewer/viewer.sdl.graphql
type Viewer {
  id: ID!
  email: String!
  profile: UserProfile!
  createdAt: DateTime!
  updatedAt: DateTime!
  accounts(first: Int, after: String, page: Int): AccountConnection!
  project(id: ID!): Project
  invitationToken(confirmationToken: String!): ProjectMemberInvitationToken
}

ソースコード: /use-cases/graphql-resolvers/src/modules/viewer/object-resolvers/viewer/viewer.sdl.graphql

こうしておくと、viewerをモックするだけで大部分のコンポーネントをレンダリングできます。

モックデータを注入するプロバイダ↓

/apps/web/src/test/MockedUrqlProvider.tsx
import { ReactNode, FC } from 'react';
import { Provider } from 'urql';
import { fromValue, never } from 'wonka';
import { aViewer } from '~/graphql/generated/mockData';

export const MockedUrqlProvider: FC<{
  children?: ReactNode;
  executeQuery?(): any;
  executeMutation?(): any;
  executeSubscription?(): any;
}> = ({ children, executeQuery, executeMutation, executeSubscription }) => (
  <Provider
    value={
      {
        executeQuery:
          executeQuery ??
          (() =>
            fromValue({
              data: {
                viewer: aViewer(),
              },
            })),
        executeMutation: executeMutation ?? jest.fn(() => never),
        executeSubscription: executeSubscription ?? jest.fn(() => never),
      } as any
    }
  >
    {children}
  </Provider>
);

ソースコード: /apps/web/src/test/MockedUrqlProvider.tsx

それを使ったコンポーネントテスト↓

import { ProjectBoard } from './ProjectBoard';
import { render } from '@testing-library/react';
import { MockedUrqlProvider } from '~/test/MockedUrqlProvider';

describe('ProjectBoard', () => {
  const renderComponent = () => {
    const renderResult = render(
      <MockedUrqlProvider>
        <ProjectBoard projectId="test" />
      </MockedUrqlProvider>
    );
    return renderResult;
  };
  test('Snapshot', () => {
    expect(renderComponent().asFragment()).toMatchSnapshot();
  });
  test('success', () => {
    const { getByText } = renderComponent();
    expect(getByText('Current')).toBeTruthy();
  });
});

モックデータの自動生成

モックデータ自体も GraphQL Code Generatorで自動生成できます。
viewerのスキーマが変われば自動でモックデータも更新され、サーバーサイドの変更の影響があるかどうかをsnapshotテストで確認できます。

Storybook

Storybookにテストできる機能があり、そっちに寄せることを検討したのですが、
Storybookのメンテがけっこう大変なので、フロントを組織的に開発するかどうかで必要度は変わって来ると思っていて、今回はStorybookごと見送りました。

テストファイルの自動生成

テストファイル自体は自動で生成しています。
PLOPを使って自動生成しています(PLOPについては以前記事を書いたのでどうぞ)

yarn g:component

などで、コンポーネントを生成できるようにしているのですが、その際にsnapshotテストを実行できる状態のテストファイルを生成できるようにしています。

PLOPは本当に便利なので、どんどん使っていきたい。

次回予告

明日はNode.jsでMailgunを使ってメールを送信するについて書きます。

1
0
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
0