mcpField
T
testing-patterns

testing-patterns

github.com/davila7テスト・品質33.0k306 回インストール今日更新
リポジトリ

testing-patterns について

React Native と Jest を使う開発者向けのテストパターン集です。テスト先行と振る舞いの検証を重視し、ファクトリー関数やカスタムレンダーでテストの一貫性を保ちます。

主な機能

  1. 01テスト駆動開発の手順
  2. 02振る舞いと公開 API の検証
  3. 03ファクトリー関数によるテストデータ作成
  4. 04必要な Provider を包むカスタムレンダー
  5. 05モジュールと GraphQL Hook のモック
  6. 06非同期クエリとユーザー操作のテスト

活用シーン

  1. 01コンポーネントの props とテストデータ用ファクトリー作成
  2. 02Provider を含む React Native 画面のテスト
  3. 03GraphQL クエリと非同期状態の検証
  4. 04フォーム操作と振る舞いのテスト作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/davila7/claude-code-templates --skill testing-patterns

インストール後、Claude Code で/testing-patternsを入力して読み込まれたか確認します。

詳細

テストパターンとユーティリティ

テストの考え方

テスト駆動開発 (TDD):

  • 最初に失敗するテストを書く
  • テストを通す最小限のコードを実装する
  • グリーンになった後にリファクタリングする
  • 失敗するテストなしにプロダクションコードを書かない

振る舞い駆動テスト:

  • 実装ではなく振る舞いをテストする
  • パブリックAPIとビジネス要件に焦点を当てる
  • 実装の詳細をテストしない
  • 振る舞いを説明する、わかりやすいテスト名を使う

ファクトリーパターン:

  • getMockX(overrides?: Partial<X>) 関数を作成する
  • 適切なデフォルト値を用意する
  • 特定のプロパティを上書きできるようにする
  • テストを DRY で保守しやすく保つ

テストユーティリティ

カスタム render 関数

必要なプロバイダーでコンポーネントをラップするカスタム render を作成します:

// src/utils/testUtils.tsx
import { render } from '@testing-library/react-native';
import { ThemeProvider } from './theme';

export const renderWithTheme = (ui: React.ReactElement) => {
  return render(
    <ThemeProvider>{ui}</ThemeProvider>
  );
};

使い方:

import { renderWithTheme } from 'utils/testUtils';
import { screen } from '@testing-library/react-native';

it('should render component', () => {
  renderWithTheme(<MyComponent />);
  expect(screen.getByText('Hello')).toBeTruthy();
});

ファクトリーパターン

コンポーネントプロパティのファクトリー
import { ComponentProps } from 'react';

const getMockMyComponentProps = (
  overrides?: Partial<ComponentProps<typeof MyComponent>>
) => {
  return {
    title: 'Default Title',
    count: 0,
    onPress: jest.fn(),
    isLoading: false,
    ...overrides,
  };
};

// Usage in tests
it('should render with custom title', () => {
  const props = getMockMyComponentProps({ title: 'Custom Title' });
  renderWithTheme(<MyComponent {...props} />);
  expect(screen.getByText('Custom Title')).toBeTruthy();
});
データファクトリー
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

const getMockUser = (overrides?: Partial<User>): User => {
  return {
    id: '123',
    name: 'John Doe',
    email: '[email protected]',
    role: 'user',
    ...overrides,
  };
};

// Usage
it('should display admin badge for admin users', () => {
  const user = getMockUser({ role: 'admin' });
  renderWithTheme(<UserCard user={user} />);
  expect(screen.getByText('Admin')).toBeTruthy();
});

モックパターン

モジュールのモック
// Mock entire module
jest.mock('utils/analytics');

// Mock with factory function
jest.mock('utils/analytics', () => ({
  Analytics: {
    logEvent: jest.fn(),
  },
}));

// Access mock in test
const mockLogEvent = jest.requireMock('utils/analytics').Analytics.logEvent;
GraphQL フックのモック
jest.mock('./GetItems.generated', () => ({
  useGetItemsQuery: jest.fn(),
}));

const mockUseGetItemsQuery = jest.requireMock(
  './GetItems.generated'
).useGetItemsQuery as jest.Mock;

// In test
mockUseGetItemsQuery.mockReturnValue({
  data: { items: [] },
  loading: false,
  error: undefined,
});

テスト構造

describe('ComponentName', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('Rendering', () => {
    it('should render component with default props', () => {});
    it('should render loading state when loading', () => {});
  });

  describe('User interactions', () => {
    it('should call onPress when button is clicked', async () => {});
  });

  describe('Edge cases', () => {
    it('should handle empty data gracefully', () => {});
  });
});

クエリパターン

// Element must exist
expect(screen.getByText('Hello')).toBeTruthy();

// Element should not exist
expect(screen.queryByText('Goodbye')).toBeNull();

// Element appears asynchronously
await waitFor(() => {
  expect(screen.findByText('Loaded')).toBeTruthy();
});

ユーザー操作パターン

import { fireEvent, screen } from '@testing-library/react-native';

it('should submit form on button click', async () => {
  const onSubmit = jest.fn();
  renderWithTheme(<LoginForm onSubmit={onSubmit} />);

  fireEvent.changeText(screen.getByLabelText('Email'), '[email protected]');
  fireEvent.changeText(screen.getByLabelText('Password'), 'password123');
  fireEvent.press(screen.getByTestId('login-button'));

  await waitFor(() => {
    expect(onSubmit).toHaveBeenCalled();
  });
});

避けるべきアンチパターン

実際の振る舞いではなくモックの振る舞いをテストする
// Bad - testing the mock
expect(mockFetchData).toHaveBeenCalled();

// Good - testing actual behavior
expect(screen.getByText('John Doe')).toBeTruthy();
ファクトリーを使用しない
// Bad - duplicated, inconsistent test data
it('test 1', () => {
  const user = { id: '1', name: 'John', email: '[email protected]', role: 'user' };
});
it('test 2', () => {
  const user = { id: '2', name: 'Jane', email: '[email protected]' }; // Missing role!
});

// Good - reusable factory
const user = getMockUser({ name: 'Custom Name' });

ベストプラクティス

  1. プロパティやデータには必ずファクトリー関数を使用する
  2. 実装ではなく振る舞いをテストする
  3. わかりやすいテスト名を使う
  4. describe ブロックで整理する
  5. テスト間でモックをクリアする
  6. テストは焦点を絞る - 1 テストにつき 1 つの振る舞い

テストの実行

# Run all tests
npm test

# Run with coverage
npm run test:coverage

# Run specific file
npm test ComponentName.test.tsx

他のスキルとの連携

  • react-ui-patterns: すべての UI 状態(読み込み中、エラー、空、成功)をテストする
  • systematic-debugging: バグを修正する前に、そのバグを再現するテストを書く

コメント