jpskill.com
💼 ビジネス コミュニティ

backend-testing

REST APIやデータベース、認証フロー、ビジネスロジックといったバックエンドのテストを、ユニットテスト、結合テスト、APIテストなどを用いて網羅的に実施し、Jest、Pytest、Mochaなどのツールやモックを活用してテスト戦略を立て、テストカバレッジを向上させるSkill。

📜 元の英語説明(参考)

Write comprehensive backend tests including unit tests, integration tests, and API tests. Use when testing REST APIs, database operations, authentication flows, or business logic. Handles Jest, Pytest, Mocha, testing strategies, mocking, and test coverage.

🇯🇵 日本人クリエイター向け解説

一言でいうと

REST APIやデータベース、認証フロー、ビジネスロジックといったバックエンドのテストを、ユニットテスト、結合テスト、APIテストなどを用いて網羅的に実施し、Jest、Pytest、Mochaなどのツールやモックを活用してテスト戦略を立て、テストカバレッジを向上させるSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o backend-testing.zip https://jpskill.com/download/20889.zip && unzip -o backend-testing.zip && rm backend-testing.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/20889.zip -OutFile "$d\backend-testing.zip"; Expand-Archive "$d\backend-testing.zip" -DestinationPath $d -Force; ri "$d\backend-testing.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して backend-testing.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → backend-testing フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

[Skill 名] backend-testing

バックエンドテスト

このスキルを使用するタイミング

このスキルを発動すべき具体的な状況は以下の通りです。

  • 新機能開発: TDD (テスト駆動開発) を使用して、まずテストを作成します。
  • APIエンドポイントの追加: REST API の成功ケースと失敗ケースをテストします。
  • バグ修正: 回帰を防ぐためにテストを追加します。
  • リファクタリング前: 既存の動作を保証するテストを作成します。
  • CI/CD セットアップ: 自動テストパイプラインを構築します。

入力形式

ユーザーから収集する情報の形式と必須/オプション情報です。

必須情報

  • フレームワーク: Express、Django、FastAPI、Spring Boot など
  • テストツール: Jest、Pytest、Mocha/Chai、JUnit など
  • テスト対象: APIエンドポイント、ビジネスロジック、DB操作 など

オプション情報

  • データベース: PostgreSQL、MySQL、MongoDB (デフォルト: インメモリDB)
  • モックライブラリ: jest.mock、sinon、unittest.mock (デフォルト: フレームワーク組み込み)
  • カバレッジ目標: 80%、90% など (デフォルト: 80%)
  • E2E ツール: Supertest、TestClient、RestAssured (オプション)

入力例

Express.js API のユーザー認証エンドポイントをテストします。
- フレームワーク: Express + TypeScript
- テストツール: Jest + Supertest
- 対象: POST /auth/register, POST /auth/login
- DB: PostgreSQL (テスト用はインメモリ)
- カバレッジ: 90%以上

手順

厳密に従うべきステップバイステップのタスク順序です。

ステップ1: テスト環境のセットアップ

テストフレームワークとツールをインストールし、設定します。

タスク:

  • テストライブラリのインストール
  • テストデータベースの設定 (インメモリまたは別のDB)
  • 環境変数の分離 (.env.test)
  • jest.config.js または pytest.ini の設定

(Node.js + Jest + Supertest):

npm install --save-dev jest ts-jest @types/jest supertest @types/supertest

jest.config.js:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.test.ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/__tests__/**'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts']
};

setup.ts (グローバルテスト設定):

import { db } from '../database';

// Reset DB before each test
beforeEach(async () => {
  await db.migrate.latest();
  await db.seed.run();
});

// Clean up after each test
afterEach(async () => {
  await db.migrate.rollback();
});

// Close connection after all tests complete
afterAll(async () => {
  await db.destroy();
});

ステップ2: 単体テストの作成 (ビジネスロジック)

個々の関数とクラスの単体テストを作成します。

タスク:

  • 純粋な関数 (依存関係なし) のテスト
  • モックによる依存関係の分離
  • エッジケース (境界値、例外) のテスト
  • AAA パターン (Arrange-Act-Assert)

決定基準:

  • 外部依存関係 (DB、API) がない場合 -> 純粋な単体テスト
  • 外部依存関係がある場合 -> Mock/Stub を使用
  • 複雑なロジックの場合 -> さまざまな入力ケースをテスト

(パスワード検証関数):

// src/utils/password.ts
export function validatePassword(password: string): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  if (password.length < 8) {
    errors.push('Password must be at least 8 characters');
  }

  if (!/[A-Z]/.test(password)) {
    errors.push('Password must contain uppercase letter');
  }

  if (!/[a-z]/.test(password)) {
    errors.push('Password must contain lowercase letter');
  }

  if (!/\d/.test(password)) {
    errors.push('Password must contain number');
  }

  if (!/[!@#$%^&*]/.test(password)) {
    errors.push('Password must contain special character');
  }

  return { valid: errors.length === 0, errors };
}

// src/__tests__/utils/password.test.ts
import { validatePassword } from '../../utils/password';

describe('validatePassword', () => {
  it('should accept valid password', () => {
    const result = validatePassword('Password123!');
    expect(result.valid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  it('should reject password shorter than 8 characters', () => {
    const result = validatePassword('Pass1!');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must be at least 8 characters');
  });

  it('should reject password without uppercase', () => {
    const result = validatePassword('password123!');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must contain uppercase letter');
  });

  it('should reject password without lowercase', () => {
    const result = validatePassword('PASSWORD123!');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must contain lowercase letter');
  });

  it('should reject password without number', () => {
    const result = validatePassword('Password!');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must contain number');
  });

  it('should reject password without special character', () => {
    const result = validatePassword('Password123');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must contain special character');
  });

  it('should return multiple errors for invalid password', () => {
    const result = validatePassword('pass');
    expect(result.valid).toBe(false);
    expect(result.errors.length).toBeGreaterThan(1);
  });
});

ステップ3: 結合テスト (APIエンドポイント)

APIエンドポイントの結合テストを作成します。

タスク:

  • HTTPリクエスト/レスポンスのテスト
  • 成功ケース (200, 201)
  • 失敗ケース (400, 401, 404, 500)
  • 認証/認可テスト
  • 入力検証テスト

チェックリスト:

  • [x] ステータスコードの検証
  • [x] レスポンスボディ構造の検証
  • [x] データベースの状態変化の確認
  • [x] エラーメッセージの検証

(Express.js + Supertest):

// src/__tests__/api/auth.test.ts
import request from 'supertes
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Backend Testing

When to use this skill

Specific situations that should trigger this skill:

  • New feature development: Write tests first using TDD (Test-Driven Development)
  • Adding API endpoints: Test success and failure cases for REST APIs
  • Bug fixes: Add tests to prevent regressions
  • Before refactoring: Write tests that guarantee existing behavior
  • CI/CD setup: Build automated test pipelines

Input Format

Format and required/optional information to collect from the user:

Required information

  • Framework: Express, Django, FastAPI, Spring Boot, etc.
  • Test tool: Jest, Pytest, Mocha/Chai, JUnit, etc.
  • Test target: API endpoints, business logic, DB operations, etc.

Optional information

  • Database: PostgreSQL, MySQL, MongoDB (default: in-memory DB)
  • Mocking library: jest.mock, sinon, unittest.mock (default: framework built-in)
  • Coverage target: 80%, 90%, etc. (default: 80%)
  • E2E tool: Supertest, TestClient, RestAssured (optional)

Input example

Test the user authentication endpoints for an Express.js API:
- Framework: Express + TypeScript
- Test tool: Jest + Supertest
- Target: POST /auth/register, POST /auth/login
- DB: PostgreSQL (in-memory for tests)
- Coverage: 90% or above

Instructions

Step-by-step task order to follow precisely.

Step 1: Set up the test environment

Install and configure the test framework and tools.

Tasks:

  • Install test libraries
  • Configure test database (in-memory or separate DB)
  • Separate environment variables (.env.test)
  • Configure jest.config.js or pytest.ini

Example (Node.js + Jest + Supertest):

npm install --save-dev jest ts-jest @types/jest supertest @types/supertest

jest.config.js:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/__tests__/**/*.test.ts'],
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/__tests__/**'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts']
};

setup.ts (global test configuration):

import { db } from '../database';

// Reset DB before each test
beforeEach(async () => {
  await db.migrate.latest();
  await db.seed.run();
});

// Clean up after each test
afterEach(async () => {
  await db.migrate.rollback();
});

// Close connection after all tests complete
afterAll(async () => {
  await db.destroy();
});

Step 2: Write Unit Tests (business logic)

Write unit tests for individual functions and classes.

Tasks:

  • Test pure functions (no dependencies)
  • Isolate dependencies via mocking
  • Test edge cases (boundary values, exceptions)
  • AAA pattern (Arrange-Act-Assert)

Decision criteria:

  • No external dependencies (DB, API) -> pure Unit Test
  • External dependencies present -> use Mock/Stub
  • Complex logic -> test various input cases

Example (password validation function):

// src/utils/password.ts
export function validatePassword(password: string): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  if (password.length < 8) {
    errors.push('Password must be at least 8 characters');
  }

  if (!/[A-Z]/.test(password)) {
    errors.push('Password must contain uppercase letter');
  }

  if (!/[a-z]/.test(password)) {
    errors.push('Password must contain lowercase letter');
  }

  if (!/\d/.test(password)) {
    errors.push('Password must contain number');
  }

  if (!/[!@#$%^&*]/.test(password)) {
    errors.push('Password must contain special character');
  }

  return { valid: errors.length === 0, errors };
}

// src/__tests__/utils/password.test.ts
import { validatePassword } from '../../utils/password';

describe('validatePassword', () => {
  it('should accept valid password', () => {
    const result = validatePassword('Password123!');
    expect(result.valid).toBe(true);
    expect(result.errors).toHaveLength(0);
  });

  it('should reject password shorter than 8 characters', () => {
    const result = validatePassword('Pass1!');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must be at least 8 characters');
  });

  it('should reject password without uppercase', () => {
    const result = validatePassword('password123!');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must contain uppercase letter');
  });

  it('should reject password without lowercase', () => {
    const result = validatePassword('PASSWORD123!');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must contain lowercase letter');
  });

  it('should reject password without number', () => {
    const result = validatePassword('Password!');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must contain number');
  });

  it('should reject password without special character', () => {
    const result = validatePassword('Password123');
    expect(result.valid).toBe(false);
    expect(result.errors).toContain('Password must contain special character');
  });

  it('should return multiple errors for invalid password', () => {
    const result = validatePassword('pass');
    expect(result.valid).toBe(false);
    expect(result.errors.length).toBeGreaterThan(1);
  });
});

Step 3: Integration Test (API endpoints)

Write integration tests for API endpoints.

Tasks:

  • Test HTTP requests/responses
  • Success cases (200, 201)
  • Failure cases (400, 401, 404, 500)
  • Authentication/authorization tests
  • Input validation tests

Checklist:

  • [x] Verify status code
  • [x] Validate response body structure
  • [x] Confirm database state changes
  • [x] Validate error messages

Example (Express.js + Supertest):

// src/__tests__/api/auth.test.ts
import request from 'supertest';
import app from '../../app';
import { db } from '../../database';

describe('POST /auth/register', () => {
  it('should register new user successfully', async () => {
    const response = await request(app)
      .post('/api/auth/register')
      .send({
        email: 'test@example.com',
        username: 'testuser',
        password: 'Password123!'
      });

    expect(response.status).toBe(201);
    expect(response.body).toHaveProperty('user');
    expect(response.body).toHaveProperty('accessToken');
    expect(response.body.user.email).toBe('test@example.com');

    // Verify the record was actually saved to DB
    const user = await db.user.findUnique({ where: { email: 'test@example.com' } });
    expect(user).toBeTruthy();
    expect(user.username).toBe('testuser');
  });

  it('should reject duplicate email', async () => {
    // Create first user
    await request(app)
      .post('/api/auth/register')
      .send({
        email: 'test@example.com',
        username: 'user1',
        password: 'Password123!'
      });

    // Second attempt with same email
    const response = await request(app)
      .post('/api/auth/register')
      .send({
        email: 'test@example.com',
        username: 'user2',
        password: 'Password123!'
      });

    expect(response.status).toBe(409);
    expect(response.body.error).toContain('already exists');
  });

  it('should reject weak password', async () => {
    const response = await request(app)
      .post('/api/auth/register')
      .send({
        email: 'test@example.com',
        username: 'testuser',
        password: 'weak'
      });

    expect(response.status).toBe(400);
    expect(response.body.error).toBeDefined();
  });

  it('should reject missing fields', async () => {
    const response = await request(app)
      .post('/api/auth/register')
      .send({
        email: 'test@example.com'
        // username, password omitted
      });

    expect(response.status).toBe(400);
  });
});

describe('POST /auth/login', () => {
  beforeEach(async () => {
    // Create test user
    await request(app)
      .post('/api/auth/register')
      .send({
        email: 'test@example.com',
        username: 'testuser',
        password: 'Password123!'
      });
  });

  it('should login with valid credentials', async () => {
    const response = await request(app)
      .post('/api/auth/login')
      .send({
        email: 'test@example.com',
        password: 'Password123!'
      });

    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('accessToken');
    expect(response.body).toHaveProperty('refreshToken');
    expect(response.body.user.email).toBe('test@example.com');
  });

  it('should reject invalid password', async () => {
    const response = await request(app)
      .post('/api/auth/login')
      .send({
        email: 'test@example.com',
        password: 'WrongPassword123!'
      });

    expect(response.status).toBe(401);
    expect(response.body.error).toContain('Invalid credentials');
  });

  it('should reject non-existent user', async () => {
    const response = await request(app)
      .post('/api/auth/login')
      .send({
        email: 'nonexistent@example.com',
        password: 'Password123!'
      });

    expect(response.status).toBe(401);
  });
});

Step 4: Authentication/Authorization Tests

Test JWT tokens and role-based access control.

Tasks:

  • Confirm 401 when accessing without a token
  • Confirm successful access with a valid token
  • Test expired token handling
  • Role-based permission tests

Example:

describe('Protected Routes', () => {
  let accessToken: string;
  let adminToken: string;

  beforeEach(async () => {
    // Regular user token
    const userResponse = await request(app)
      .post('/api/auth/register')
      .send({
        email: 'user@example.com',
        username: 'user',
        password: 'Password123!'
      });
    accessToken = userResponse.body.accessToken;

    // Admin token
    const adminResponse = await request(app)
      .post('/api/auth/register')
      .send({
        email: 'admin@example.com',
        username: 'admin',
        password: 'Password123!'
      });
    // Update role to 'admin' in DB
    await db.user.update({
      where: { email: 'admin@example.com' },
      data: { role: 'admin' }
    });
    // Log in again to get a new token
    const loginResponse = await request(app)
      .post('/api/auth/login')
      .send({
        email: 'admin@example.com',
        password: 'Password123!'
      });
    adminToken = loginResponse.body.accessToken;
  });

  describe('GET /api/auth/me', () => {
    it('should return current user with valid token', async () => {
      const response = await request(app)
        .get('/api/auth/me')
        .set('Authorization', `Bearer ${accessToken}`);

      expect(response.status).toBe(200);
      expect(response.body.user.email).toBe('user@example.com');
    });

    it('should reject request without token', async () => {
      const response = await request(app)
        .get('/api/auth/me');

      expect(response.status).toBe(401);
    });

    it('should reject request with invalid token', async () => {
      const response = await request(app)
        .get('/api/auth/me')
        .set('Authorization', 'Bearer invalid-token');

      expect(response.status).toBe(403);
    });
  });

  describe('DELETE /api/users/:id (Admin only)', () => {
    it('should allow admin to delete user', async () => {
      const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });

      const response = await request(app)
        .delete(`/api/users/${targetUser.id}`)
        .set('Authorization', `Bearer ${adminToken}`);

      expect(response.status).toBe(200);
    });

    it('should forbid non-admin from deleting user', async () => {
      const targetUser = await db.user.findUnique({ where: { email: 'user@example.com' } });

      const response = await request(app)
        .delete(`/api/users/${targetUser.id}`)
        .set('Authorization', `Bearer ${accessToken}`);

      expect(response.status).toBe(403);
    });
  });
});

Step 5: Mocking and Test Isolation

Mock external dependencies to isolate tests.

Tasks:

  • Mock external APIs
  • Mock email sending
  • Mock file system
  • Mock time-related functions

Example (mocking an external API):

// src/services/emailService.ts
export async function sendVerificationEmail(email: string, token: string): Promise<void> {
  const response = await fetch('https://api.sendgrid.com/v3/mail/send', {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${process.env.SENDGRID_API_KEY}` },
    body: JSON.stringify({
      to: email,
      subject: 'Verify your email',
      html: `<a href="https://example.com/verify?token=${token}">Verify</a>`
    })
  });

  if (!response.ok) {
    throw new Error('Failed to send email');
  }
}

// src/__tests__/services/emailService.test.ts
import { sendVerificationEmail } from '../../services/emailService';

// Mock fetch
global.fetch = jest.fn();

describe('sendVerificationEmail', () => {
  beforeEach(() => {
    (fetch as jest.Mock).mockClear();
  });

  it('should send email successfully', async () => {
    (fetch as jest.Mock).mockResolvedValueOnce({
      ok: true,
      status: 200
    });

    await expect(sendVerificationEmail('test@example.com', 'token123'))
      .resolves
      .toBeUndefined();

    expect(fetch).toHaveBeenCalledWith(
      'https://api.sendgrid.com/v3/mail/send',
      expect.objectContaining({
        method: 'POST'
      })
    );
  });

  it('should throw error if email sending fails', async () => {
    (fetch as jest.Mock).mockResolvedValueOnce({
      ok: false,
      status: 500
    });

    await expect(sendVerificationEmail('test@example.com', 'token123'))
      .rejects
      .toThrow('Failed to send email');
  });
});

Output format

Defines the exact format that outputs must follow.

Basic structure

project/
├── src/
│   ├── __tests__/
│   │   ├── setup.ts                 # Global test configuration
│   │   ├── utils/
│   │   │   └── password.test.ts     # Unit tests
│   │   ├── services/
│   │   │   └── emailService.test.ts
│   │   └── api/
│   │       ├── auth.test.ts         # Integration tests
│   │       └── users.test.ts
│   └── ...
├── jest.config.js
└── package.json

Test run scripts (package.json)

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --maxWorkers=2"
  }
}

Coverage report

$ npm run test:coverage

--------------------------|---------|----------|---------|---------|
File                      | % Stmts | % Branch | % Funcs | % Lines |
--------------------------|---------|----------|---------|---------|
All files                 |   92.5  |   88.3   |   95.2  |   92.8  |
 auth/                    |   95.0  |   90.0   |  100.0  |   95.0  |
  middleware.ts           |   95.0  |   90.0   |  100.0  |   95.0  |
  routes.ts               |   95.0  |   90.0   |  100.0  |   95.0  |
 utils/                   |   90.0  |   85.0   |   90.0  |   90.0  |
  password.ts             |   90.0  |   85.0   |   90.0  |   90.0  |
--------------------------|---------|----------|---------|---------|

Constraints

Rules and prohibitions that must be strictly followed.

Required rules (MUST)

  1. Test isolation: Each test must be runnable independently

    • Reset state with beforeEach/afterEach
    • Do not depend on test execution order
  2. Clear test names: The name must convey what the test verifies

    • ✅ 'should reject duplicate email'
    • ❌ 'test1'
  3. AAA pattern: Arrange (setup) - Act (execute) - Assert (verify) structure

    • Improves readability
    • Clarifies test intent

Prohibited (MUST NOT)

  1. No production DB: Tests must use a separate or in-memory DB

    • Risk of losing real data
    • Cannot isolate tests
  2. No real external API calls: Mock all external services

    • Removes network dependency
    • Speeds up tests
    • Reduces costs
  3. No Sleep/Timeout abuse: Use fake timers for time-based tests

    • jest.useFakeTimers()
    • Prevents test slowdowns

Security rules

  • No hardcoded secrets: Never hardcode API keys or passwords in test code
  • Separate environment variables: Use .env.test file

Examples

Example 1: Python FastAPI tests (Pytest)

Situation: Testing a FastAPI REST API

User request:

Test the user API built with FastAPI using pytest.

Final result:

# tests/conftest.py
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from app.main import app
from app.database import Base, get_db

# In-memory SQLite for tests
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

@pytest.fixture(scope="function")
def db_session():
    Base.metadata.create_all(bind=engine)
    db = TestingSessionLocal()
    try:
        yield db
    finally:
        db.close()
        Base.metadata.drop_all(bind=engine)

@pytest.fixture(scope="function")
def client(db_session):
    def override_get_db():
        try:
            yield db_session
        finally:
            db_session.close()

    app.dependency_overrides[get_db] = override_get_db
    yield TestClient(app)
    app.dependency_overrides.clear()

# tests/test_auth.py
def test_register_user_success(client):
    response = client.post("/auth/register", json={
        "email": "test@example.com",
        "username": "testuser",
        "password": "Password123!"
    })

    assert response.status_code == 201
    assert "access_token" in response.json()
    assert response.json()["user"]["email"] == "test@example.com"

def test_register_duplicate_email(client):
    # First user
    client.post("/auth/register", json={
        "email": "test@example.com",
        "username": "user1",
        "password": "Password123!"
    })

    # Duplicate email
    response = client.post("/auth/register", json={
        "email": "test@example.com",
        "username": "user2",
        "password": "Password123!"
    })

    assert response.status_code == 409
    assert "already exists" in response.json()["detail"]

def test_login_success(client):
    # Register
    client.post("/auth/register", json={
        "email": "test@example.com",
        "username": "testuser",
        "password": "Password123!"
    })

    # Login
    response = client.post("/auth/login", json={
        "email": "test@example.com",
        "password": "Password123!"
    })

    assert response.status_code == 200
    assert "access_token" in response.json()

def test_protected_route_without_token(client):
    response = client.get("/auth/me")
    assert response.status_code == 401

def test_protected_route_with_token(client):
    # Register and get token
    register_response = client.post("/auth/register", json={
        "email": "test@example.com",
        "username": "testuser",
        "password": "Password123!"
    })
    token = register_response.json()["access_token"]

    # Access protected route
    response = client.get("/auth/me", headers={
        "Authorization": f"Bearer {token}"
    })

    assert response.status_code == 200
    assert response.json()["email"] == "test@example.com"

Best practices

Quality improvements

  1. TDD (Test-Driven Development): Write tests before writing code

    • Clarifies requirements
    • Improves design
    • Naturally achieves high coverage
  2. Given-When-Then pattern: Write tests in BDD style

    it('should return 404 when user not found', async () => {
      // Given: a non-existent user ID
      const nonExistentId = 'non-existent-uuid';
    
      // When: attempting to look up that user
      const response = await request(app).get(`/users/${nonExistentId}`);
    
      // Then: 404 response
      expect(response.status).toBe(404);
    });
  3. Test Fixtures: Reusable test data

    const validUser = {
      email: 'test@example.com',
      username: 'testuser',
      password: 'Password123!'
    };

Efficiency improvements

  • Parallel execution: Speed up tests with Jest's --maxWorkers option
  • Snapshot Testing: Save snapshots of UI components or JSON responses
  • Coverage thresholds: Enforce minimum coverage in jest.config.js

Common Issues

Issue 1: Test failures caused by shared state between tests

Symptom: Passes individually but fails when run together

Cause: DB state shared due to missing beforeEach/afterEach

Fix:

beforeEach(async () => {
  await db.migrate.rollback();
  await db.migrate.latest();
});

Issue 2: "Jest did not exit one second after the test run"

Symptom: Process does not exit after tests complete

Cause: DB connections, servers, etc. not cleaned up

Fix:

afterAll(async () => {
  await db.destroy();
  await server.close();
});

Issue 3: Async test timeout

Symptom: "Timeout - Async callback was not invoked"

Cause: Missing async/await or unhandled Promise

Fix:

// Bad
it('should work', () => {
  request(app).get('/users');  // Promise not handled
});

// Good
it('should work', async () => {
  await request(app).get('/users');
});

References

Official docs

Learning resources

Tools

Metadata

Version

  • Current version: 1.0.0
  • Last updated: 2025-01-01
  • Compatible platforms: Claude, ChatGPT, Gemini

Related skills

Tags

#testing #backend #Jest #Pytest #unit-test #integration-test #TDD #API-test