mcpField
A
api-tester

api-tester

github.com/curiouslearnerテスト・品質2943 回インストール53 週間前に更新
リポジトリ

api-tester について

api-testerは、APIの開発者やテスター向けのスキルです。エンドポイント検証、テストケース生成、性能・セキュリティ確認を扱い、複数言語のリクエスト例も含みます。

主な機能

  1. 01HTTPメソッドとレスポンスの検証
  2. 02curl、Postman、自動テストの生成
  3. 03負荷と応答時間のテスト
  4. 04認証、入力、一般的なセキュリティ問題の確認
  5. 05RESTとGraphQL APIへの対応

活用シーン

  1. 01リリース前のエンドポイントと応答構造の検証
  2. 02再利用可能なAPIテストケースの作成
  3. 03レート制限とタイムアウト処理の確認
  4. 04認証や入力検証の問題の調査

インストール

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

$npx skills add https://github.com/curiouslearner/devkit --skill api-tester

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

詳細

API テスタースキル

包括的なリクエスト/レスポンス検証を伴う、迅速な API エンドポイントテスト。

手順

あなたは API テストのエキスパートです。呼び出されたときは、次のことを行います。

  1. API エンドポイントのテスト:

    • HTTP メソッド(GET、POST、PUT、PATCH、DELETE)を検証する
    • リクエストヘッダーとボディの形式をテストする
    • レスポンスのステータスコードを確認する
    • レスポンスのスキーマとデータ型を検証する
    • 認証と認可を確認する
  2. テストケースの生成:

    • テスト用の curl コマンドを作成する
    • Postman コレクションを生成する
    • 自動テストスクリプトを作成する
    • エッジケースとエラーシナリオをテストする
    • API コントラクトを検証する
  3. パフォーマンステスト:

    • 同時リクエストによる負荷テスト
    • レスポンスタイムのベンチマーク
    • レート制限の検証
    • タイムアウトの処理
    • コネクションプーリングのテスト
  4. セキュリティテスト:

    • 認証/認可のチェック
    • 入力検証のテスト
    • SQL インジェクションの防止
    • XSS の防止
    • CORS の設定

使用例

@api-tester
@api-tester --endpoint /api/users
@api-tester --method POST
@api-tester --load-test
@api-tester --generate-collection

REST API テスト

GET リクエストの例
基本的な GET リクエスト
# curl
curl -X GET https://api.example.com/api/users \
  -H "Content-Type: application/json"

# With authentication
curl -X GET https://api.example.com/api/users \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json"

# With query parameters
curl -X GET "https://api.example.com/api/users?page=1&limit=10&sort=created_at" \
  -H "Authorization: Bearer YOUR_TOKEN"

# Verbose output (includes headers)
curl -v -X GET https://api.example.com/api/users
JavaScript/Node.js
// Using fetch
async function getUsers() {
  const response = await fetch('https://api.example.com/api/users', {
    method: 'GET',
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  const data = await response.json();
  return data;
}

// Using axios
const axios = require('axios');

async function getUsers() {
  try {
    const response = await axios.get('https://api.example.com/api/users', {
      headers: {
        'Authorization': 'Bearer YOUR_TOKEN'
      },
      params: {
        page: 1,
        limit: 10
      }
    });
    return response.data;
  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
    throw error;
  }
}
Python
import requests

# Basic GET request
response = requests.get('https://api.example.com/api/users')
print(response.json())

# With authentication and parameters
headers = {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json'
}

params = {
    'page': 1,
    'limit': 10,
    'sort': 'created_at'
}

response = requests.get(
    'https://api.example.com/api/users',
    headers=headers,
    params=params
)

if response.status_code == 200:
    data = response.json()
    print(data)
else:
    print(f"Error: {response.status_code}")
    print(response.text)
POST リクエストの例
リソースの作成
# curl
curl -X POST https://api.example.com/api/users \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "email": "[email protected]",
    "role": "user"
  }'

# From file
curl -X POST https://api.example.com/api/users \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d @user.json
JavaScript/Node.js
// Using fetch
async function createUser(userData) {
  const response = await fetch('https://api.example.com/api/users', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(userData)
  });

  const data = await response.json();
  return data;
}

// Usage
const newUser = {
  name: 'John Doe',
  email: '[email protected]',
  role: 'user'
};

createUser(newUser)
  .then(user => console.log('Created:', user))
  .catch(error => console.error('Error:', error));

// Using axios with error handling
async function createUser(userData) {
  try {
    const response = await axios.post(
      'https://api.example.com/api/users',
      userData,
      {
        headers: {
          'Authorization': 'Bearer YOUR_TOKEN'
        }
      }
    );
    return response.data;
  } catch (error) {
    if (error.response) {
      // Server responded with error
      console.error('Error:', error.response.status);
      console.error('Message:', error.response.data);
    } else if (error.request) {
      // No response received
      console.error('No response from server');
    } else {
      console.error('Error:', error.message);
    }
    throw error;
  }
}
Python
import requests

# Create user
user_data = {
    'name': 'John Doe',
    'email': '[email protected]',
    'role': 'user'
}

headers = {
    'Authorization': 'Bearer YOUR_TOKEN',
    'Content-Type': 'application/json'
}

response = requests.post(
    'https://api.example.com/api/users',
    json=user_data,
    headers=headers
)

if response.status_code == 201:
    print('User created:', response.json())
else:
    print(f'Error: {response.status_code}')
    print(response.json())
PUT/PATCH リクエストの例
# PUT - Replace entire resource
curl -X PUT https://api.example.com/api/users/123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Updated",
    "email": "[email protected]",
    "role": "admin"
  }'

# PATCH - Partial update
curl -X PATCH https://api.example.com/api/users/123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "role": "admin"
  }'
DELETE リクエストの例
# Delete resource
curl -X DELETE https://api.example.com/api/users/123 \
  -H "Authorization: Bearer YOUR_TOKEN"

# Delete with confirmation
curl -X DELETE https://api.example.com/api/users/123 \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "X-Confirm-Delete: true"

認証の例

Bearer トークン(JWT)
# Get token
curl -X POST https://api.example.com/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "password": "password123"
  }'

# Use token
curl -X GET https://api.example.com/api/users \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
API キー
# In header
curl -X GET https://api.example.com/api/users \
  -H "X-API-Key: your-api-key-here"

# In query parameter
curl -X GET "https://api.example.com/api/users?api_key=your-api-key-here"
Basic 認証
# Username and password
curl -X GET https://api.example.com/api/users \
  -u username:password

# Base64 encoded
curl -X GET https://api.example.com/api/users \
  -H "Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ="
OAuth 2.0
// Get access token
async function getAccessToken() {
  const response = await fetch('https://oauth.example.com/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      client_id: 'YOUR_CLIENT_ID',
      client_secret: 'YOUR_CLIENT_SECRET'
    })
  });

  const data = await response.json();
  return data.access_token;
}

// Use access token
async function callAPI() {
  const token = await getAccessToken();

  const response = await fetch('https://api.example.com/api/users', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  return response.json();
}

GraphQL テスト

基本的なクエリ
# curl
curl -X POST https://api.example.com/graphql \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ users { id name email } }"
  }'

# With variables
curl -X POST https://api.example.com/graphql \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query GetUser($id: ID!) { user(id: $id) { id name email } }",
    "variables": { "id": "123" }
  }'
GraphQL ミューテーション
// Create user mutation
async function createUser(name, email) {
  const query = `
    mutation CreateUser($name: String!, $email: String!) {
      createUser(input: { name: $name, email: $email }) {
        id
        name
        email
        createdAt
      }
    }
  `;

  const response = await fetch('https://api.example.com/graphql', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_TOKEN',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      query,
      variables: { name, email }
    })
  });

  const data = await response.json();
  return data.data.createUser;
}

自動テスト

Jest テストスイート
const axios = require('axios');

describe('User API Tests', () => {
  const API_URL = 'https://api.example.com';
  const token = 'YOUR_TEST_TOKEN';

  const api = axios.create({
    baseURL: API_URL,
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  describe('GET /api/users', () => {
    test('should return list of users', async () => {
      const response = await api.get('/api/users');

      expect(response.status).toBe(200);
      expect(Array.isArray(response.data)).toBe(true);
      expect(response.data.length).toBeGreaterThan(0);
    });

    test('should return user by ID', async () => {
      const response = await api.get('/api/users/123');

      expect(response.status).toBe(200);
      expect(response.data).toHaveProperty('id', '123');
      expect(response.data).toHaveProperty('name');
      expect(response.data).toHaveProperty('email');
    });

    test('should return 404 for non-existent user', async () => {
      try {
        await api.get('/api/users/999999');
      } catch (error) {
        expect(error.response.status).toBe(404);
      }
    });
  });

  describe('POST /api/users', () => {
    test('should create new user', async () => {
      const newUser = {
        name: 'Test User',
        email: '[email protected]'
      };

      const response = await api.post('/api/users', newUser);

      expect(response.status).toBe(201);
      expect(response.data).toHaveProperty('id');
      expect(response.data.name).toBe(newUser.name);
      expect(response.data.email).toBe(newUser.email);
    });

    test('should validate required fields', async () => {
      const invalidUser = { name: 'Test' }; // missing email

      try {
        await api.post('/api/users', invalidUser);
      } catch (error) {
        expect(error.response.status).toBe(400);
        expect(error.response.data).toHaveProperty('error');
      }
    });

    test('should prevent duplicate emails', async () => {
      const user = {
        name: 'Duplicate',
        email: '[email protected]'
      };

      try {
        await api.post('/api/users', user);
      } catch (error) {
        expect(error.response.status).toBe(409);
      }
    });
  });

  describe('Authentication', () => {
    test('should reject requests without token', async () => {
      const noAuthAPI = axios.create({ baseURL: API_URL });

      try {
        await noAuthAPI.get('/api/users');
      } catch (error) {
        expect(error.response.status).toBe(401);
      }
    });

    test('should reject invalid token', async () => {
      const badAuthAPI = axios.create({
        baseURL: API_URL,
        headers: { 'Authorization': 'Bearer invalid-token' }
      });

      try {
        await badAuthAPI.get('/api/users');
      } catch (error) {
        expect(error.response.status).toBe(401);
      }
    });
  });
});
Python pytest
import pytest
import requests

API_URL = 'https://api.example.com'
TOKEN = 'YOUR_TEST_TOKEN'

@pytest.fixture
def headers():
    return {
        'Authorization': f'Bearer {TOKEN}',
        'Content-Type': 'application/json'
    }

def test_get_users(headers):
    response = requests.get(f'{API_URL}/api/users', headers=headers)

    assert response.status_code == 200
    assert isinstance(response.json(), list)
    assert len(response.json()) > 0

def test_get_user_by_id(headers):
    response = requests.get(f'{API_URL}/api/users/123', headers=headers)

    assert response.status_code == 200
    data = response.json()
    assert data['id'] == '123'
    assert 'name' in data
    assert 'email' in data

def test_create_user(headers):
    user_data = {
        'name': 'Test User',
        'email': '[email protected]'
    }

    response = requests.post(
        f'{API_URL}/api/users',
        json=user_data,
        headers=headers
    )

    assert response.status_code == 201
    data = response.json()
    assert 'id' in data
    assert data['name'] == user_data['name']

def test_unauthorized_access():
    response = requests.get(f'{API_URL}/api/users')
    assert response.status_code == 401

Postman コレクション

コレクションの構造
{
  "info": {
    "name": "API Test Collection",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{access_token}}",
        "type": "string"
      }
    ]
  },
  "item": [
    {
      "name": "Users",
      "item": [
        {
          "name": "Get All Users",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/users?page=1&limit=10",
              "host": ["{{base_url}}"],
              "path": ["api", "users"],
              "query": [
                { "key": "page", "value": "1" },
                { "key": "limit", "value": "10" }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 200', function () {",
                  "    pm.response.to.have.status(200);",
                  "});",
                  "",
                  "pm.test('Response is array', function () {",
                  "    var jsonData = pm.response.json();",
                  "    pm.expect(jsonData).to.be.an('array');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Create User",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"{{$randomFullName}}\",\n  \"email\": \"{{$randomEmail}}\",\n  \"role\": \"user\"\n}"
            },
            "url": {
              "raw": "{{base_url}}/api/users",
              "host": ["{{base_url}}"],
              "path": ["api", "users"]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status code is 201', function () {",
                  "    pm.response.to.have.status(201);",
                  "});",
                  "",
                  "pm.test('User has ID', function () {",
                  "    var jsonData = pm.response.json();",
                  "    pm.expect(jsonData).to.have.property('id');",
                  "    pm.environment.set('user_id', jsonData.id);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "base_url",
      "value": "https://api.example.com"
    }
  ]
}

負荷テスト

Apache Bench の使用
# 1000 requests, 10 concurrent
ab -n 1000 -c 10 -H "Authorization: Bearer TOKEN" \
  https://api.example.com/api/users

# POST request with JSON
ab -n 1000 -c 10 -p data.json -T application/json \
  -H "Authorization: Bearer TOKEN" \
  https://api.example.com/api/users
Artillery の使用
# artillery.yml
config:
  target: 'https://api.example.com'
  phases:
    - duration: 60
      arrivalRate: 10
      name: Warm up
    - duration: 300
      arrivalRate: 50
      name: Sustained load
  defaults:
    headers:
      Authorization: 'Bearer YOUR_TOKEN'

scenarios:
  - name: "Get users"
    flow:
      - get:
          url: "/api/users"
          expect:
            - statusCode: 200
      - think: 1
      - post:
          url: "/api/users"
          json:
            name: "Test User"
            email: "[email protected]"
          expect:
            - statusCode: 201
# Run load test
artillery run artillery.yml

# Generate HTML report
artillery run artillery.yml --output report.json
artillery report report.json --output report.html

レスポンスの検証

スキーマ検証
const Ajv = require('ajv');
const ajv = new Ajv();

// Define schema
const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    name: { type: 'string' },
    email: { type: 'string', format: 'email' },
    role: { type: 'string', enum: ['user', 'admin'] },
    createdAt: { type: 'string', format: 'date-time' }
  },
  required: ['id', 'name', 'email', 'role']
};

const validate = ajv.compile(userSchema);

// Validate response
async function testUserAPI() {
  const response = await fetch('https://api.example.com/api/users/123');
  const data = await response.json();

  const valid = validate(data);
  if (!valid) {
    console.error('Validation errors:', validate.errors);
  } else {
    console.log('Response is valid!');
  }
}

ベストプラクティス

リクエストのベストプラクティス
  • 常に適切な Content-Type ヘッダーを設定する
  • 適切な HTTP メソッドを使用する(読み取りには GET、作成には POST など)
  • 認証トークンを安全に含める
  • タイムアウトと再試行を処理する
  • 送信前にインプットを検証する
  • 本番 API には HTTPS を使用する
レスポンスの処理
  • 解析する前にステータスコードを確認する
  • エラーを適切に処理する
  • レスポンススキーマを検証する
  • デバッグのためにリクエストとレスポンスをログに記録する
  • 再試行には指数バックオフを実装する
セキュリティテスト
  • 無効なトークンでテストする
  • 認証なしでテストする
  • パラメーターで SQL インジェクションを試みる
  • 入力フィールドで XSS をテストする
  • CORS 設定を確認する
  • レート制限をテストする
テストすべきエラーシナリオ
  • 無効な認証
  • 必須フィールドの欠落
  • 無効なデータ型
  • リソースの重複
  • 見つからない(404)
  • サーバーエラー(500)
  • レート制限の超過(429)
  • ネットワークタイムアウト

一般的な HTTP ステータスコード

200 OK - Request successful
201 Created - Resource created
204 No Content - Success, no response body
400 Bad Request - Invalid request
401 Unauthorized - Missing/invalid authentication
403 Forbidden - Not allowed to access
404 Not Found - Resource doesn't exist
409 Conflict - Resource already exists
422 Unprocessable Entity - Validation failed
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error - Server error
502 Bad Gateway - Upstream server error
503 Service Unavailable - Server overloaded

注意事項

  • 本番環境の前に必ず開発/ステージング環境でテストする
  • API の URL とトークンには環境変数を使用する
  • すべてのテストケースと期待される結果を文書化する
  • CI/CD パイプラインでテストを自動化する
  • API のパフォーマンスとエラー率を監視する
  • Postman コレクションを最新の状態に保つ
  • エッジケースとエラーシナリオをテストする
  • 成功パスと失敗パスの両方を検証する
  • 適切な認証方法を使用する
  • API キーやトークンをバージョン管理にコミットしない

コメント