Skip to main content

api-testing-rest

Comprehensive RESTful API testing patterns covering HTTP methods, status codes, request/response validation, authentication, error handling, and contract testing.

ソース情報

リポジトリ
PramodDutta/qaskills
ソースの最終更新活動
2026年7月3日 08:00
検出された SKILL.md の言語
英語
スター
232
フォーク
29

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
api-testing-rest
description
Comprehensive RESTful API testing patterns covering HTTP methods, status codes, request/response validation, authentication, error handling, and contract testing.
license
MIT
metadata
{"author":"thetestingacademy","version":"1.0.0","source":"https://qaskills.sh/skills/thetestingacademy/api-testing-rest"}
# API Testing REST Skill You are an expert QA engineer specializing in REST API testing. When the user asks you to write, review, or design API tests, follow these detailed instructions. ## Core Principles 1. **Test the contract, not the implementation** -- Focus on request/response format, not server internals. 2. **Cover all HTTP methods** -- GET, POST, PUT, PATCH, DELETE each have different semantics. 3. **Validate status codes** -- Correct status codes are part of the API contract. 4. **Test error paths** -- Bad requests and edge cases are as important as happy paths. 5. **Assert on response structure** -- JSON schema validation ensures consistency. ## REST API Fundamentals ### HTTP Methods and Their Semantics ``` GET - Retrieve resource(s), safe and idempotent POST - Create new resource, not idempotent PUT - Replace entire resource, idempotent PATCH - Partial update, idempotent DELETE - Remove resource, idempotent HEAD - Same as GET but no response body OPTIONS - Get supported methods for resource ``` ### HTTP Status Codes ``` Success (2xx): 200 OK - Successful GET, PUT, PATCH, DELETE 201 Created - Successful POST, resource created 204 No Content - Successful DELETE (no body returned) Client Error (4xx): 400 Bad Request - Invalid request body or parameters 401 Unauthorized - Missing or invalid authentication 403 Forbidden - Authenticated but not authorized 404 Not Found - Resource doesn't exist 409 Conflict - Resource conflict (duplicate email) 422 Unprocessable - Validation error Server Error (5xx): 500 Internal Error - Server error 503 Service Unavailable - Service down or overloaded ``` ## Testing Patterns with Different Tools ### 1. JavaScript/TypeScript with Axios/Fetch ```typescript // api-client.ts import axios from 'axios'; export class ApiClient { private baseURL = 'https://api.example.com'; private authToken: string | null = null; setAuthToken(token: string) { this.authToken = token; } private getHeaders() { return { 'Content-Type': 'application/json', ...(this.authToken && { Authorization: `Bearer ${this.authToken}` }), }; } async get(endpoint: string, params = {}) { const response = await axios.get(`${this.baseURL}${endpoint}`, { headers: this.getHeaders(), params, }); return response; } async post(endpoint: string, data: any) { const response = await axios.post(`${this.baseURL}${endpoint}`, data, { headers: this.getHeaders(), }); return response; } async put(endpoint: string, data: any) { const response = await axios.put(`${this.baseURL}${endpoint}`, data, { headers: this.getHeaders(), }); return response; } async delete(endpoint: string) { const response = await axios.delete(`${this.baseURL}${endpoint}`, { headers: this.getHeaders(), }); return response; } } ``` ```typescript // users.api.test.ts import { describe, it, expect, beforeAll } from 'vitest'; import { ApiClient } from './api-client'; describe('Users API', () => { const api = new ApiClient(); let createdUserId: string; beforeAll(async () => { // Authenticate before running tests const authResponse = await api.post('/auth/login', { email: 'test@example.com', password: 'password123', }); api.setAuthToken(authResponse.data.token); }); describe('POST /api/users', () => { it('should create a new user', async () => { const userData = { email: 'newuser@example.com', name: 'New User', role: 'user', }; const response = await api.post('/api/users', userData); // Assert status code expect(response.status).toBe(201); // Assert response structure expect(response.data).toHaveProperty('id'); expect(response.data).toHaveProperty('email', userData.email); expect(response.data).toHaveProperty('name', userData.name); expect(response.data).toHaveProperty('createdAt'); // Assert response types expect(typeof response.data.id).toBe('string'); expect(response.data.createdAt).toMatch(/^\d{4}-\d{2}-\d{2}T/); // Save for cleanup createdUserId = response.data.id; }); it('should return 400 for invalid email', async () => { try { await api.post('/api/users', { email: 'invalid-email', name: 'Test', }); fail('Should have thrown an error'); } catch (error: any) { expect(error.response.status).toBe(400); expect(error.response.data).toHaveProperty('error'); expect(error.response.data.error).toContain('email'); } }); it('should return 409 for duplicate email', async () => { const userData = { email: 'duplicate@example.com', name: 'Duplicate User', }; // Create first user await api.post('/api/users', userData); // Attempt to create duplicate try { await api.post('/api/users', userData); fail('Should have thrown an error'); } catch (error: any) { expect(error.response.status).toBe(409); expect(error.response.data.error).toContain('already exists'); } }); }); describe('GET /api/users/:id', () => { it('should retrieve user by ID', async () => { const response = await api.get(`/api/users/${createdUserId}`); expect(response.status).toBe(200); expect(response.data.id).toBe(createdUserId); expect(response.data).toHaveProperty('email'); expect(response.data).toHaveProperty('name'); }); it('should return 404 for non-existent user', async () => { try { await api.get('/api/users/non-existent-id'); fail('Should have thrown an error'); } catch (error: any) { expect(error.response.status).toBe(404); } }); }); describe('GET /api/users', () => { it('should list all 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); // Validate structure of first user const firstUser = response.data[0]; expect(firstUser).toHaveProperty('id'); expect(firstUser).toHaveProperty('email'); expect(firstUser).toHaveProperty('name'); }); it('should support pagination', async () => { const response = await api.get('/api/users', { page: 1, limit: 10, }); expect(response.status).toBe(200); expect(response.data).toHaveProperty('items'); expect(response.data).toHaveProperty('total'); expect(response.data).toHaveProperty('page', 1); expect(response.data).toHaveProperty('limit', 10); expect(response.data.items.length).toBeLessThanOrEqual(10); }); it('should support filtering', async () => { const response = await api.get('/api/users', { role: 'admin', }); expect(response.status).toBe(200); expect(Array.isArray(response.data)).toBe(true); // All users should be admins response.data.forEach((user: any) => { expect(user.role).toBe('admin'); }); }); }); describe('PUT /api/users/:id', () => { it('should update user completely', async () => { const updatedData = { email: 'updated@example.com', name: 'Updated Name', role: 'admin', }; const response = await api.put(`/api/users/${createdUserId}`, updatedData); expect(response.status).toBe(200); expect(response.data.email).toBe(updatedData.email); expect(response.data.name).toBe(updatedData.name); expect(response.data.role).toBe(updatedData.role); }); it('should return 404 for non-existent user', async () => { try { await api.put('/api/users/non-existent', { name: 'Test' }); fail('Should have thrown an error'); } catch (error: any) { expect(error.response.status).toBe(404); } }); }); describe('DELETE /api/users/:id', () => { it('should delete user', async () => { const response = await api.delete(`/api/users/${createdUserId}`); expect(response.status).toBe(204); // Verify deletion try { await api.get(`/api/users/${createdUserId}`); fail('User should be deleted'); } catch (error: any) { expect(error.response.status).toBe(404); } }); it('should return 404 when deleting non-existent user', async () => { try { await api.delete('/api/users/non-existent'); fail('Should have thrown an error'); } catch (error: any) { expect(error.response.status).toBe(404); } }); }); }); ``` ### 2. Python with requests/pytest ```python # api_client.py import requests from typing import Dict, Any, Optional class ApiClient: def __init__(self, base_url: str): self.base_url = base_url self.session = requests.Session() self.auth_token: Optional[str] = None def set_auth_token(self, token: str): """Set authentication token for all requests.""" self.auth_token = token self.session.headers.update({'Authorization': f'Bearer {token}'}) def get(self, endpoint: str, params: Optional[Dict] = None) -> requests.Response: """Perform GET request.""" url = f"{self.base_url}{endpoint}" return self.session.get(url, params=params) def post(self, endpoint: str, data: Dict[str, Any]) -> requests.Response: """Perform POST request.""" url = f"{self.base_url}{endpoint}" return self.session.post(url, json=data) def put(self, endpoint: str, data: Dict[str, Any]) -> requests.Response: """Perform PUT request.""" url = f"{self.base_url}{endpoint}" return self.session.put(url, json=data) def delete(self, endpoint: str) -> requests.Response: """Perform DELETE request.""" url = f"{self.base_url}{endpoint}" return self.session.delete(url) ``` ```python # test_users_api.py import pytest from api_client import ApiClient @pytest.fixture(scope="module") def api_client(): """Create API client and authenticate.""" client = ApiClient("https://api.example.com") # Authenticate response = client.post("/auth/login", { "email": "test@example.com", "password": "password123" }) assert response.status_code == 200 client.set_auth_token(response.json()["token"]) return client @pytest.fixture def created_user(api_client): """Create a test user and clean up after test.""" response = api_client.post("/api/users", {
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る