mcpField
T
testing-patterns

testing-patterns

github.com/sickn33テスト・品質48.7k1.1k 回インストール今日更新
リポジトリ

testing-patterns について

Jest でテストを書く開発者向けのスキルです。TDD、振る舞いのテスト、ファクトリー関数、モック、コンポーネントテストのパターンを扱います。

主な機能

  1. 01TDD のレッド・グリーン・リファクタリング
  2. 02振る舞いと公開 API のテスト
  3. 03テストデータとプロパティのファクトリー
  4. 04モジュールと GraphQL Hook のモック
  5. 05コンポーネントのクエリと操作

活用シーン

  1. 01React Native コンポーネントのテスト
  2. 02ファクトリーによる再利用可能なテストデータ作成
  3. 03失敗するテストで問題を再現して修正

インストール

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

$npx skills add https://github.com/sickn33/agentic-awesome-skills --skill testing-patterns

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

詳細

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

テストの理念

テスト駆動開発(TDD):

  • まず失敗するテストを書く
  • テストを通すために最小限のコードを実装する
  • テストが成功したらリファクタリングする
  • 失敗するテストを書かずに本番コードを書かない

振る舞い駆動テスト:

  • 実装ではなく振る舞いをテストする
  • パブリック API とビジネス要件に重点を置く
  • 実装の詳細をテストしない
  • 振る舞いを表す、説明的なテスト名を付ける

ファクトリーパターン:

  • getMockX(overrides?: Partial<X>) 関数を作成する
  • 妥当なデフォルト値を設定する
  • 特定のプロパティを上書きできるようにする
  • テストの重複を避け、保守しやすくする

テストユーティリティ

カスタムレンダー関数

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

// 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();
});

ファクトリーパターン

コンポーネントの Props ファクトリー
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.todo('should render component with default props');
    it.todo('should render loading state when loading');
  });

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

  describe('Edge cases', () => {
    it.todo('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
// async-appearance-example: await the element, not the Promise
expect(await screen.findByText('Loaded')).toBeTruthy();

ユーザー操作のパターン

import { fireEvent, screen, waitFor } 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. Props とデータには必ずファクトリー関数を使用する
  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:修正前に、バグを再現するテストを書く

使用するタイミング

Jest のユニットテスト、テストファクトリー、モックを書く場合や、レッド・グリーン・リファクターサイクルを通じて失敗するテストを診断する場合に使用します。

制限事項

  • タスクが上記の範囲に明確に該当する場合にのみ、このスキルを使用してください。
  • 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
  • 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中断して確認してください。

コメント