Skip to Content
GuideHow-tosAPI Testing

API Testing

This guide shows you how to build comprehensive API testing workflows with Probe. You’ll learn to test REST APIs thoroughly, validate responses, handle authentication, and implement advanced testing patterns.

Basic API Testing

Simple GET Request Test

Start with a basic API endpoint test:

name: Basic API Test description: Test a simple REST API endpoint vars: api_base_url: "{{API_BASE_URL ?? 'https://jsonplaceholder.typicode.com'}}" timeout: "{{TIMEOUT ?? '30s'}}" jobs: - id: basic-api-test name: Basic API Test defaults: http: timeout: "{{vars.timeout}}" headers: Accept: "application/json" User-Agent: "Probe API Tester v1.0" steps: - name: Get Posts uses: http with: method: GET url: "{{vars.api_base_url}}/posts" test: | res.code == 200 && res.headers["Content-Type"] contains "application/json" && res.body != null && len(res.body) > 0 outputs: post_count: len(res.body) first_post_id: res.body[0].id response_time: (rt.sec * 1000) - name: Get Single Post uses: http with: method: GET url: "{{vars.api_base_url}}/posts/{{outputs.first_post_id}}" test: | res.code == 200 && res.body.id == outputs.first_post_id && res.body.title != null && res.body.body != null outputs: post_title: res.body.title post_body: res.body.body - name: Test Results Summary uses: hello echo: | 📊 API Test Results: Posts Retrieved: {{outputs.post_count}} First Post ID: {{outputs.first_post_id}} Post Title: "{{outputs.post_title}}" Response Time: {{outputs.response_time}}ms ✅ Basic API tests completed successfully

CRUD Operations Testing

Test Create, Read, Update, Delete operations:

name: CRUD API Testing description: Test complete CRUD operations on a REST API vars: api_base_url: "{{API_BASE_URL ?? 'https://jsonplaceholder.typicode.com'}}" test_user_id: "{{TEST_USER_ID ?? '1'}}" jobs: - id: crud-operations name: CRUD Operations Test steps: # CREATE - POST Request - name: Create New Post id: create uses: http with: url: "{{vars.api_base_url}}/posts" method: POST headers: Content-Type: "application/json" body: | { "title": "Test Post {{random_str(6)}}", "body": "This is a test post created by Probe at {{unixtime()}}", "userId": {{vars.test_user_id}} } test: | res.code == 201 && res.body.id != null && res.body.title != null && res.body.userId == {{vars.test_user_id}} outputs: created_post_id: res.body.id created_title: res.body.title created_body: res.body.body # READ - GET Request - name: Read Created Post uses: http with: method: GET url: "{{vars.api_base_url}}/posts/{{outputs.create.created_post_id}}" test: | res.code == 200 && res.body.id == outputs.create.created_post_id && res.body.title == "{{outputs.create.created_title}}" outputs: read_success: true # UPDATE - PUT Request - name: Update Post id: update uses: http with: url: "{{vars.api_base_url}}/posts/{{outputs.create.created_post_id}}" method: PUT headers: Content-Type: "application/json" body: | { "id": {{outputs.create.created_post_id}}, "title": "Updated: {{outputs.create.created_title}}", "body": "This post was updated by Probe at {{unixtime()}}", "userId": {{vars.test_user_id}} } test: | res.code == 200 && res.body.id == outputs.create.created_post_id && res.body.title startsWith "Updated:" outputs: updated_title: res.body.title # PARTIAL UPDATE - PATCH Request - name: Partial Update Post uses: http with: url: "{{vars.api_base_url}}/posts/{{outputs.create.created_post_id}}" method: PATCH headers: Content-Type: "application/json" body: | { "title": "Patched: {{outputs.update.updated_title}}" } test: | res.code == 200 && res.body.title startsWith "Patched:" outputs: patched_title: res.body.title # DELETE - DELETE Request - name: Delete Post uses: http with: url: "{{vars.api_base_url}}/posts/{{outputs.create.created_post_id}}" method: DELETE test: res.code == 200 outputs: deleted: true # VERIFY DELETION - name: Verify Deletion uses: http with: method: GET url: "{{vars.api_base_url}}/posts/{{outputs.create.created_post_id}}" test: res.code == 404 outputs: deletion_verified: res.code == 404 - name: CRUD Test Summary uses: hello echo: | 🔄 CRUD Operations Test Summary: ✅ CREATE: Post ID {{outputs.create.created_post_id}} created Title: "{{outputs.create.created_title}}" ✅ READ: Successfully retrieved created post ✅ UPDATE: Title updated to "{{outputs.update.updated_title}}" ✅ PATCH: Title patched to "{{outputs.patched_title}}" ✅ DELETE: Post deletion {{outputs.deleted ? "successful" : "failed"}} ✅ VERIFY: Deletion {{outputs.deletion_verified ? "verified (404)" : "not verified"}} All CRUD operations completed successfully!

Authentication Testing

Bearer Token Authentication

name: Bearer Token API Testing description: Test APIs with Bearer token authentication vars: api_base_url: "{{API_BASE_URL ?? 'https://api.yourapp.com'}}" auth_url: "{{AUTH_URL ?? 'https://auth.yourapp.com'}}" test_username: "{{TEST_USERNAME ?? 'test@example.com'}}" test_password: "{{TEST_PASSWORD ?? 'test_password_123'}}" client_id: "{{CLIENT_ID}}" client_secret: "{{CLIENT_SECRET}}" jobs: - id: authentication-flow name: Authentication Flow Test steps: # Step 1: Obtain Bearer Token - name: Login and Get Token id: login uses: http with: url: "{{vars.auth_url}}/oauth/token" method: POST headers: Content-Type: "application/json" body: | { "grant_type": "password", "username": "{{vars.test_username}}", "password": "{{vars.test_password}}", "client_id": "{{vars.client_id}}", "client_secret": "{{vars.client_secret}}" } test: | res.code == 200 && res.body.access_token != null && res.body.token_type == "Bearer" && res.body.expires_in > 0 outputs: access_token: res.body.access_token refresh_token: res.body.refresh_token expires_in: res.body.expires_in token_type: res.body.token_type # Step 2: Test Authenticated Endpoint - name: Get User Profile id: profile uses: http with: method: GET url: "{{vars.api_base_url}}/user/profile" headers: Authorization: "{{outputs.login.token_type}} {{outputs.login.access_token}}" Accept: "application/json" test: | res.code == 200 && res.body.id != null && res.body.email == "{{vars.test_username}}" outputs: user_id: res.body.id user_email: res.body.email user_name: res.body.name # Step 3: Test Protected Resource - name: Access Protected Resource uses: http with: method: GET url: "{{vars.api_base_url}}/user/{{outputs.profile.user_id}}/data" headers: Authorization: "{{outputs.login.token_type}} {{outputs.login.access_token}}" test: | res.code == 200 && res.body.user_id == outputs.profile.user_id outputs: protected_data_accessible: true # Step 4: Test Token Refresh - name: Refresh Token id: refresh uses: http with: url: "{{vars.auth_url}}/oauth/token" method: POST headers: Content-Type: "application/json" body: | { "grant_type": "refresh_token", "refresh_token": "{{outputs.login.refresh_token}}", "client_id": "{{vars.client_id}}", "client_secret": "{{vars.client_secret}}" } test: | res.code == 200 && res.body.access_token != null && res.body.access_token != "{{outputs.login.access_token}}" outputs: new_access_token: res.body.access_token # Step 5: Test with New Token - name: Test New Token uses: http with: method: GET url: "{{vars.api_base_url}}/user/profile" headers: Authorization: "Bearer {{outputs.refresh.new_access_token}}" test: res.code == 200 outputs: new_token_valid: true - id: unauthorized-access-test name: Unauthorized Access Test steps: # Test without token - name: Test No Authorization uses: http with: method: GET url: "{{vars.api_base_url}}/user/profile" test: res.code == 401 outputs: no_auth_rejected: res.code == 401 # Test with invalid token - name: Test Invalid Token uses: http with: method: GET url: "{{vars.api_base_url}}/user/profile" headers: Authorization: "Bearer invalid_token_123" test: res.code == 401 outputs: invalid_token_rejected: res.code == 401 # Test with expired token (if available) - name: Test Expired Token uses: http with: method: GET url: "{{vars.api_base_url}}/user/profile" headers: Authorization: "Bearer {{vars.expired_token}}" test: res.code == 401 outputs: expired_token_rejected: res.code == 401 - id: security-test-summary name: Security Test Summary needs: [authentication-flow, unauthorized-access-test] steps: - name: Authentication Summary uses: hello echo: | 🔐 Authentication & Authorization Test Results: AUTHENTICATION FLOW: ✅ Login: {{outputs['authentication-flow'].access_token ? "Token obtained" : "Failed"}} ✅ Profile Access: {{outputs['authentication-flow'].user_email ? "Success" : "Failed"}} ✅ Protected Resource: {{outputs['authentication-flow'].protected_data_accessible ? "Accessible" : "Failed"}} ✅ Token Refresh: {{outputs['authentication-flow'].new_access_token ? "Success" : "Failed"}} ✅ New Token Valid: {{outputs['authentication-flow'].new_token_valid ? "Yes" : "No"}} SECURITY VALIDATION: ✅ No Auth Rejected: {{outputs['unauthorized-access-test'].no_auth_rejected ? "Yes (401)" : "Security Issue!"}} ✅ Invalid Token Rejected: {{outputs['unauthorized-access-test'].invalid_token_rejected ? "Yes (401)" : "Security Issue!"}} {{vars.expired_token ? "✅ Expired Token Rejected: " + (outputs['unauthorized-access-test'].expired_token_rejected ? "Yes (401)" : "Security Issue!") : ""}} USER INFORMATION: User ID: {{outputs['authentication-flow'].user_id}} Email: {{outputs['authentication-flow'].user_email}} Name: {{outputs['authentication-flow'].user_name}} Token Expires In: {{outputs['authentication-flow'].expires_in}} seconds

API Key Authentication

name: API Key Authentication Testing description: Test APIs using API key authentication vars: api_base_url: "{{API_BASE_URL ?? 'https://api.yourservice.com'}}" api_key: "{{API_KEY}}" rate_limit_tier: "{{RATE_LIMIT_TIER ?? 'premium'}}" jobs: - id: api-key-tests name: API Key Authentication Tests steps: # Header-based API Key - name: Test API Key in Header uses: http with: method: GET url: "{{vars.api_base_url}}/data" headers: X-API-Key: "{{vars.api_key}}" Accept: "application/json" test: | res.code == 200 && res.body.authenticated == true && res.body.rate_limit != null outputs: header_auth_success: true rate_limit_remaining: res.body.rate_limit.remaining rate_limit_reset: res.body.rate_limit.reset_time # Query Parameter API Key - name: Test API Key in Query uses: http with: method: GET url: "{{vars.api_base_url}}/data?api_key={{vars.api_key}}" test: res.code == 200 outputs: query_auth_success: true # Test Rate Limiting - name: Test Rate Limit Info uses: http with: method: GET url: "{{vars.api_base_url}}/rate-limit-status" headers: X-API-Key: "{{vars.api_key}}" test: | res.code == 200 && res.body.tier == "{{vars.rate_limit_tier}}" && res.body.requests_remaining > 0 outputs: requests_remaining: res.body.requests_remaining requests_per_hour: res.body.limits.per_hour current_usage: res.body.current_usage # Test Invalid API Key - name: Test Invalid API Key uses: http with: method: GET url: "{{vars.api_base_url}}/data" headers: X-API-Key: "invalid_key_123" test: res.code == 401 || res.code == 403 outputs: invalid_key_rejected: res.code == 401 || res.code == 403 - name: API Key Test Summary uses: hello echo: | 🔑 API Key Authentication Results: Header Authentication: {{outputs.header_auth_success ? "✅ Success" : "❌ Failed"}} Query Authentication: {{outputs.query_auth_success ? "✅ Success" : "❌ Failed"}} Invalid Key Rejected: {{outputs.invalid_key_rejected ? "✅ Yes" : "❌ Security Issue"}} Rate Limiting: Tier: {{vars.rate_limit_tier}} Requests Remaining: {{outputs.requests_remaining}}/{{outputs.requests_per_hour}} Current Usage: {{outputs.current_usage}} Reset Time: {{outputs.rate_limit_reset}}

Data Validation and Response Testing

JSON Schema Validation

name: JSON Response Validation description: Validate API responses against expected schemas vars: API_BASE_URL: https://api.yourservice.com jobs: - id: response-validation name: Response Schema Validation steps: - name: Get User List id: users uses: http with: method: GET url: "{{vars.api_base_url}}/users" headers: Authorization: "Bearer {{vars.api_token}}" test: | res.code == 200 && res.body != null && res.body.data != null && len(res.body.data) > 0 && # Validate response structure res.body.meta != null && res.body.meta.total != null && res.body.meta.page != null && res.body.meta.per_page != null && # Validate first user object res.body.data[0].id != null && res.body.data[0].email != null && res.body.data[0].name != null && res.body.data[0].created_at != null && # Validate data types typeof(res.body.data[0].id) == "number" && typeof(res.body.data[0].email) == "string" && typeof(res.body.data[0].active) == "boolean" outputs: total_users: res.body.meta.total users_per_page: res.body.meta.per_page first_user_id: res.body.data[0].id first_user_email: res.body.data[0].email - name: Get Single User Details uses: http with: method: GET url: "{{vars.api_base_url}}/users/{{outputs.users.first_user_id}}" headers: Authorization: "Bearer {{vars.api_token}}" test: | res.code == 200 && res.body.user != null && # Validate required fields res.body.user.id == outputs.users.first_user_id && res.body.user.email == "{{outputs.users.first_user_email}}" && res.body.user.profile != null && # Validate nested objects res.body.user.profile.first_name != null && res.body.user.profile.last_name != null && res.body.user.preferences != null && # Validate arrays res.body.user.roles != null && len(res.body.user.roles) > 0 && res.body.user.permissions != null outputs: user_roles: res.body.user.roles user_permissions_count: len(res.body.user.permissions) profile_complete: res.body.user.profile.first_name != null && res.body.user.profile.last_name != null - name: Test Data Consistency uses: http with: method: GET url: "{{vars.api_base_url}}/users/{{outputs.users.first_user_id}}/orders" headers: Authorization: "Bearer {{vars.api_token}}" test: | res.code == 200 && res.body.orders != null && # Validate all orders belong to the user all(res.body.orders, #.user_id == outputs.users.first_user_id) && # Validate order structure all(res.body.orders, #.id != null && #.total != null && #.status != null && #.created_at != null) && # Validate business logic all(res.body.orders, #.total >= 0) && all(filter(res.body.orders, #.status == "completed"), #.completed_at != null) outputs: order_count: len(res.body.orders) completed_orders: len(filter(res.body.orders, #.status == "completed")) total_spent: sum(map(filter(res.body.orders, #.status == "completed"), #.total)) - name: Validation Summary uses: hello echo: | 📋 Data Validation Results: USER LIST VALIDATION: ✅ Response Structure: Valid pagination metadata ✅ User Objects: All required fields present ✅ Data Types: Correct types for all fields Total Users: {{outputs.users.total_users}} Users Per Page: {{outputs.users.users_per_page}} USER DETAILS VALIDATION: ✅ User Profile: {{outputs.profile_complete ? "Complete" : "Incomplete"}} ✅ Security: {{len(outputs.user_roles)}} roles, {{outputs.user_permissions_count}} permissions DATA CONSISTENCY: ✅ Orders: {{outputs.order_count}} total orders ✅ Completed: {{outputs.completed_orders}} completed orders ✅ Business Logic: All orders have valid totals and timestamps Total Customer Value: ${{outputs.total_spent}}

Error Response Validation

name: Error Response Validation description: Test error handling and response formats vars: API_BASE_URL: https://api.yourservice.com jobs: - id: error-response-tests name: Error Response Tests steps: # Test 400 - Bad Request - name: Test Bad Request uses: http with: url: "{{vars.api_base_url}}/users" method: POST headers: Content-Type: "application/json" Authorization: "Bearer {{vars.api_token}}" body: | { "email": "invalid-email", "name": "" } test: | res.code == 400 && res.body.error != null && res.body.error.code == "validation_error" && res.body.error.message != null && res.body.error.details != null && len(res.body.error.details) > 0 outputs: validation_errors: res.body.error.details error_code: res.body.error.code # Test 401 - Unauthorized - name: Test Unauthorized Access uses: http with: method: GET url: "{{vars.api_base_url}}/admin/users" test: | res.code == 401 && res.body.error != null && res.body.error.code == "unauthorized" && res.body.error.message contains "authentication" outputs: auth_error_proper: true # Test 403 - Forbidden - name: Test Forbidden Access uses: http with: method: GET url: "{{vars.api_base_url}}/admin/users" headers: Authorization: "Bearer {{vars.USER_TOKEN}}" # Non-admin token test: | res.code == 403 && res.body.error != null && res.body.error.code == "forbidden" && res.body.error.message contains "permission" outputs: permission_error_proper: true # Test 404 - Not Found - name: Test Not Found uses: http with: method: GET url: "{{vars.api_base_url}}/users/99999999" headers: Authorization: "Bearer {{vars.api_token}}" test: | res.code == 404 && res.body.error != null && res.body.error.code == "not_found" && res.body.error.resource == "user" outputs: not_found_error_proper: true # Test 422 - Unprocessable Entity - name: Test Unprocessable Entity uses: http with: url: "{{vars.api_base_url}}/users" method: POST headers: Content-Type: "application/json" Authorization: "Bearer {{vars.api_token}}" body: | { "email": "existing@example.com", "name": "Test User" } test: | res.code == 422 && res.body.error != null && res.body.error.code == "unprocessable_entity" && res.body.error.details != null outputs: duplicate_email_handled: true # Test Rate Limiting - 429 - name: Test Rate Limiting uses: http with: method: GET url: "{{vars.api_base_url}}/high-rate-endpoint" headers: Authorization: "Bearer {{vars.LIMITED_TOKEN}}" test: | res.status in [200, 429] && (res.code == 429 ? res.body.error.code == "rate_limit_exceeded" && res.headers["Retry-After"] != null : true ) outputs: rate_limiting_works: res.code == 429 - name: Error Handling Summary uses: hello echo: | 🚨 Error Response Validation Results: 400 Bad Request: ✅ Proper validation error format Error Code: {{outputs.error_code}} Validation Issues: {{len(outputs.validation_errors)}} 401 Unauthorized: {{outputs.auth_error_proper ? "✅ Proper format" : "❌ Issues detected"}} 403 Forbidden: {{outputs.permission_error_proper ? "✅ Proper format" : "❌ Issues detected"}} 404 Not Found: {{outputs.not_found_error_proper ? "✅ Proper format" : "❌ Issues detected"}} 422 Unprocessable: {{outputs.duplicate_email_handled ? "✅ Business logic validated" : "❌ Issues detected"}} 429 Rate Limited: {{outputs.rate_limiting_works ? "✅ Rate limiting active" : "ℹ️ No rate limit hit"}} All error responses follow consistent format and provide helpful information.

Advanced API Testing Patterns

Workflow Testing

name: E-commerce Workflow Testing description: Test complete e-commerce user workflow vars: API_BASE_URL: https://api.ecommerce.com TEST_PRODUCT_ID: 123 TEST_USER_EMAIL: test@example.com jobs: - id: user-registration-flow name: User Registration Workflow steps: - name: Register New User id: register uses: http with: url: "{{vars.api_base_url}}/auth/register" method: POST headers: Content-Type: "application/json" body: | { "email": "test{{random_str(8)}}@example.com", "password": "TestPassword123!", "first_name": "Test", "last_name": "User", "phone": "+1234567890" } test: | res.code == 201 && res.body.user.id != null && res.body.user.email != null && res.body.access_token != null outputs: user_id: res.body.user.id user_email: res.body.user.email access_token: res.body.access_token - name: Email Verification Check id: user-registration-flow uses: http with: method: GET url: "{{vars.api_base_url}}/user/profile" headers: Authorization: "Bearer {{outputs.register.access_token}}" test: | res.code == 200 && res.body.email_verified == false && res.body.verification_email_sent == true outputs: verification_pending: true - id: shopping-flow name: Shopping Workflow needs: [user-registration-flow] steps: - name: Browse Products id: browse uses: http with: method: GET url: "{{vars.api_base_url}}/products?category=electronics&limit=10" test: | res.code == 200 && len(res.body.products) > 0 && res.body.products[0].id != null && res.body.products[0].price > 0 outputs: available_products: len(res.body.products) first_product_id: res.body.products[0].id first_product_price: res.body.products[0].price - name: Add to Cart id: add-cart uses: http with: url: "{{vars.api_base_url}}/cart/items" method: POST headers: Content-Type: "application/json" Authorization: "Bearer {{outputs['user-registration-flow'].access_token}}" body: | { "product_id": {{outputs.browse.first_product_id}}, "quantity": 2 } test: | res.code == 201 && res.body.cart_item.id != null && res.body.cart_item.quantity == 2 && res.body.cart_total > 0 outputs: cart_item_id: res.body.cart_item.id cart_total: res.body.cart_total - name: View Cart id: shopping-flow uses: http with: method: GET url: "{{vars.api_base_url}}/cart" headers: Authorization: "Bearer {{outputs['user-registration-flow'].access_token}}" test: | res.code == 200 && len(res.body.items) == 1 && res.body.items[0].product_id == outputs.browse.first_product_id && res.body.total == outputs['add-cart'].cart_total outputs: cart_verified: true - id: checkout-flow name: Checkout Workflow needs: [shopping-flow] steps: - name: Apply Discount Code id: discount uses: http with: url: "{{vars.api_base_url}}/cart/discount" method: POST headers: Content-Type: "application/json" Authorization: "Bearer {{outputs['user-registration-flow'].access_token}}" body: | { "code": "TESTDISCOUNT10" } test: | res.code == 200 && res.body.discount_applied == true && res.body.discount_amount > 0 && res.body.new_total < outputs['shopping-flow'].cart_total outputs: discount_applied: res.code == 200 discount_amount: res.body.discount_amount final_total: res.body.new_total - name: Add Payment Method id: payment uses: http with: url: "{{vars.api_base_url}}/payment-methods" method: POST headers: Content-Type: "application/json" Authorization: "Bearer {{outputs['user-registration-flow'].access_token}}" body: | { "type": "credit_card", "card_number": "4111111111111111", "expiry_month": 12, "expiry_year": 2025, "cvv": "123", "name": "Test User" } test: | res.code == 201 && res.body.payment_method.id != null && res.body.payment_method.last_four == "1111" outputs: payment_method_id: res.body.payment_method.id - name: Create Order id: order uses: http with: url: "{{vars.api_base_url}}/orders" method: POST headers: Content-Type: "application/json" Authorization: "Bearer {{outputs['user-registration-flow'].access_token}}" body: | { "payment_method_id": {{outputs.payment.payment_method_id}}, "shipping_address": { "street": "123 Test St", "city": "Test City", "state": "TS", "zip": "12345", "country": "US" } } test: | res.code == 201 && res.body.order.id != null && res.body.order.status == "processing" && res.body.order.total > 0 outputs: order_id: res.body.order.id order_status: res.body.order.status order_total: res.body.order.total - name: Verify Order Processing id: checkout-flow uses: http with: method: GET url: "{{vars.api_base_url}}/orders/{{outputs.order.order_id}}" headers: Authorization: "Bearer {{outputs['user-registration-flow'].access_token}}" test: | res.code == 200 && res.body.order.id == outputs.order.order_id && res.body.order.user_id == outputs['user-registration-flow'].user_id && len(res.body.order.items) > 0 outputs: order_verified: true - id: workflow-summary name: Workflow Test Summary needs: [user-registration-flow, shopping-flow, checkout-flow] steps: - name: Complete Workflow Results uses: hello echo: | 🛒 E-commerce Workflow Test Results: ===================================== USER REGISTRATION: ✅ User Created: {{outputs['user-registration-flow'].user_email}} ✅ Authentication: Token obtained ✅ Email Verification: {{outputs['user-registration-flow'].verification_pending ? "Pending (as expected)" : "Issue detected"}} SHOPPING EXPERIENCE: ✅ Product Browse: {{outputs['shopping-flow'].available_products}} products found ✅ Add to Cart: Product ID {{outputs['shopping-flow'].first_product_id}} added ✅ Cart Verification: {{outputs['shopping-flow'].cart_verified ? "Confirmed" : "Failed"}} Cart Total: ${{outputs['shopping-flow'].cart_total}} CHECKOUT PROCESS: {{outputs['checkout-flow'].discount_applied ? "✅ Discount Applied: $" + outputs['checkout-flow'].discount_amount + " off" : "ℹ️ No discount applied"}} ✅ Payment Method: Added (ending in 1111) ✅ Order Created: Order ID {{outputs['checkout-flow'].order_id}} ✅ Order Status: {{outputs['checkout-flow'].order_status}} ✅ Order Verified: {{outputs['checkout-flow'].order_verified ? "Confirmed" : "Failed"}} Final Total: ${{outputs['checkout-flow'].order_total}} 🎉 Complete e-commerce workflow tested successfully! User can register, browse, shop, and checkout without issues.

Performance and Load Testing

Response Time Testing

name: API Performance Testing description: Test API response times and performance characteristics vars: API_BASE_URL: https://api.yourservice.com PERFORMANCE_THRESHOLD_MS: 1000 ACCEPTABLE_THRESHOLD_MS: 2000 jobs: - id: response-time-tests name: Response Time Performance Tests steps: - name: Lightweight Endpoint Test id: ping uses: http with: method: GET url: "{{vars.api_base_url}}/ping" test: | res.code == 200 && (rt.sec * 1000) < 500 outputs: ping_time: (rt.sec * 1000) ping_fast: (rt.sec * 1000) < 200 - name: Database Query Test id: db-query uses: http with: method: GET url: "{{vars.api_base_url}}/users?limit=100" headers: Authorization: "Bearer {{vars.api_token}}" test: | res.code == 200 && (rt.sec * 1000) < {{vars.PERFORMANCE_THRESHOLD_MS}} outputs: query_time: (rt.sec * 1000) query_performance: | {{(rt.sec * 1000) < 500 ? "excellent" : (rt.sec * 1000) < 1000 ? "good" : (rt.sec * 1000) < 2000 ? "acceptable" : "poor"}} - name: Complex Aggregation Test id: aggregation uses: http with: method: GET url: "{{vars.api_base_url}}/analytics/summary" headers: Authorization: "Bearer {{vars.api_token}}" test: | res.code == 200 && (rt.sec * 1000) < {{vars.ACCEPTABLE_THRESHOLD_MS}} outputs: aggregation_time: (rt.sec * 1000) aggregation_acceptable: (rt.sec * 1000) < {{vars.ACCEPTABLE_THRESHOLD_MS}} - name: File Upload Test id: upload uses: http with: url: "{{vars.api_base_url}}/files/upload" method: POST headers: Authorization: "Bearer {{vars.api_token}}" Content-Type: "multipart/form-data" body: | --boundary123 Content-Disposition: form-data; name="file"; filename="test.txt" Content-Type: text/plain This is a test file for upload performance testing. It contains multiple lines of text to simulate a real file. --boundary123-- test: | res.code == 201 && (rt.sec * 1000) < 5000 outputs: upload_time: (rt.sec * 1000) upload_acceptable: (rt.sec * 1000) < 3000 - name: Performance Summary uses: hello echo: | ⚡ API Performance Test Results: ENDPOINT PERFORMANCE: Ping: {{outputs.ping_time}}ms {{outputs.ping_fast ? "(🚀 Fast)" : "(⚡ OK)"}} Database Query: {{outputs.query_time}}ms ({{outputs.query_performance}}) Complex Aggregation: {{outputs.aggregation_time}}ms {{outputs.aggregation_acceptable ? "(✅ Acceptable)" : "(⚠️ Slow)"}} File Upload: {{outputs.upload_time}}ms {{outputs.upload_acceptable ? "(✅ Acceptable)" : "(⚠️ Slow)"}} PERFORMANCE CLASSIFICATION: {{outputs.ping_time < 200 && outputs.query_time < 500 && outputs.aggregation_time < 1000 ? "🟢 EXCELLENT - All endpoints performing optimally" : ""}} {{outputs.ping_time < 500 && outputs.query_time < 1000 && outputs.aggregation_time < 2000 ? "🟡 GOOD - Performance within acceptable ranges" : ""}} {{outputs.aggregation_time > 2000 || outputs.upload_time > 5000 ? "🔴 NEEDS ATTENTION - Some endpoints are slow" : ""}} RECOMMENDATIONS: {{outputs.query_time > 800 ? "• Consider database query optimization" : ""}} {{outputs.aggregation_time > 1500 ? "• Review aggregation query efficiency" : ""}} {{outputs.upload_time > 3000 ? "• Optimize file upload handling" : ""}}

Best Practices

1. Test Structure

# Good: Organized test structure jobs: authentication: # Group related tests crud-operations: # Clear test categories error-handling: # Logical organization performance: # Separate concerns

2. Data Management

# Good: Use random data for isolation body: | { "email": "test{{random_str(8)}}@example.com", "username": "user_{{unixtime()}}_{{random_str(4)}}" } # Good: Clean up test data - name: Cleanup Test User uses: http with: url: "{{vars.api_base_url}}/users/{{outputs.create.user_id}}" method: DELETE

3. Comprehensive Validation

# Good: Validate multiple aspects test: | res.code == 200 && # HTTP status res.headers["Content-Type"] contains "json" && # Content type res.body.id != null && # Required fields typeof(res.body.id) == "number" && # Data types res.body.email contains "@" && # Data format (rt.sec * 1000) < 1000 # Performance

4. Error Handling

# Good: Test error scenarios - name: Test Invalid Input uses: http with: body: '{"invalid": "data"}' test: res.code == 400 # Good: Validate error responses test: | res.code == 400 && res.body.error.code == "validation_error" && len(res.body.error.details) > 0

What’s Next?

Now that you can test APIs comprehensively, explore:

API testing is crucial for reliable services. Use these patterns to build comprehensive test suites that catch issues before they reach production.

Updated at