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:
yaml
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'}}"
defaults:
http:
timeout: "{{vars.timeout}}"
headers:
Accept: "application/json"
User-Agent: "Probe API Tester v1.0"
jobs:
basic-api-test:
name: Basic API Test
steps:
- name: Get Posts
action: http
with:
url: "{{vars.api_base_url}}/posts"
test: |
res.status == 200 &&
res.headers["content-type"].contains("application/json") &&
res.json != null &&
res.json.length > 0
outputs:
post_count: res.json.length
first_post_id: res.json[0].id
response_time: res.time
- name: Get Single Post
action: http
with:
url: "{{vars.api_base_url}}/posts/{{outputs.first_post_id}}"
test: |
res.status == 200 &&
res.json.id == outputs.first_post_id &&
res.json.title != null &&
res.json.body != null
outputs:
post_title: res.json.title
post_body: res.json.body
- name: Test Results Summary
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:
yaml
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:
crud-operations:
name: CRUD Operations Test
steps:
# CREATE - POST Request
- name: Create New Post
id: create
action: 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.status == 201 &&
res.json.id != null &&
res.json.title != null &&
res.json.userId == {{vars.test_user_id}}
outputs:
created_post_id: res.json.id
created_title: res.json.title
created_body: res.json.body
# READ - GET Request
- name: Read Created Post
action: http
with:
url: "{{vars.api_base_url}}/posts/{{outputs.create.created_post_id}}"
test: |
res.status == 200 &&
res.json.id == outputs.create.created_post_id &&
res.json.title == "{{outputs.create.created_title}}"
outputs:
read_success: true
# UPDATE - PUT Request
- name: Update Post
id: update
action: 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.status == 200 &&
res.json.id == outputs.create.created_post_id &&
res.json.title.startsWith("Updated:")
outputs:
updated_title: res.json.title
# PARTIAL UPDATE - PATCH Request
- name: Partial Update Post
action: 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.status == 200 &&
res.json.title.startsWith("Patched:")
outputs:
patched_title: res.json.title
# DELETE - DELETE Request
- name: Delete Post
action: http
with:
url: "{{vars.api_base_url}}/posts/{{outputs.create.created_post_id}}"
method: DELETE
test: res.status == 200
outputs:
deleted: true
# VERIFY DELETION
- name: Verify Deletion
action: http
with:
url: "{{vars.api_base_url}}/posts/{{outputs.create.created_post_id}}"
test: res.status == 404
continue_on_error: true
outputs:
deletion_verified: res.status == 404
- name: CRUD Test Summary
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
yaml
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:
authentication-flow:
name: Authentication Flow Test
steps:
# Step 1: Obtain Bearer Token
- name: Login and Get Token
id: login
action: 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.status == 200 &&
res.json.access_token != null &&
res.json.token_type == "Bearer" &&
res.json.expires_in > 0
outputs:
access_token: res.json.access_token
refresh_token: res.json.refresh_token
expires_in: res.json.expires_in
token_type: res.json.token_type
# Step 2: Test Authenticated Endpoint
- name: Get User Profile
id: profile
action: http
with:
url: "{{vars.api_base_url}}/user/profile"
headers:
Authorization: "{{outputs.login.token_type}} {{outputs.login.access_token}}"
Accept: "application/json"
test: |
res.status == 200 &&
res.json.id != null &&
res.json.email == "{{vars.test_username}}"
outputs:
user_id: res.json.id
user_email: res.json.email
user_name: res.json.name
# Step 3: Test Protected Resource
- name: Access Protected Resource
action: http
with:
url: "{{vars.api_base_url}}/user/{{outputs.profile.user_id}}/data"
headers:
Authorization: "{{outputs.login.token_type}} {{outputs.login.access_token}}"
test: |
res.status == 200 &&
res.json.user_id == outputs.profile.user_id
outputs:
protected_data_accessible: true
# Step 4: Test Token Refresh
- name: Refresh Token
id: refresh
action: 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.status == 200 &&
res.json.access_token != null &&
res.json.access_token != "{{outputs.login.access_token}}"
outputs:
new_access_token: res.json.access_token
# Step 5: Test with New Token
- name: Test New Token
action: http
with:
url: "{{vars.api_base_url}}/user/profile"
headers:
Authorization: "Bearer {{outputs.refresh.new_access_token}}"
test: res.status == 200
outputs:
new_token_valid: true
unauthorized-access-test:
name: Unauthorized Access Test
steps:
# Test without token
- name: Test No Authorization
action: http
with:
url: "{{vars.api_base_url}}/user/profile"
test: res.status == 401
outputs:
no_auth_rejected: res.status == 401
# Test with invalid token
- name: Test Invalid Token
action: http
with:
url: "{{vars.api_base_url}}/user/profile"
headers:
Authorization: "Bearer invalid_token_123"
test: res.status == 401
outputs:
invalid_token_rejected: res.status == 401
# Test with expired token (if available)
- name: Test Expired Token
if: vars.expired_token
action: http
with:
url: "{{vars.api_base_url}}/user/profile"
headers:
Authorization: "Bearer {{vars.expired_token}}"
test: res.status == 401
outputs:
expired_token_rejected: res.status == 401
security-test-summary:
name: Security Test Summary
needs: [authentication-flow, unauthorized-access-test]
steps:
- name: Authentication Summary
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
yaml
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:
api-key-tests:
name: API Key Authentication Tests
steps:
# Header-based API Key
- name: Test API Key in Header
action: http
with:
url: "{{vars.api_base_url}}/data"
headers:
X-API-Key: "{{vars.api_key}}"
Accept: "application/json"
test: |
res.status == 200 &&
res.json.authenticated == true &&
res.json.rate_limit != null
outputs:
header_auth_success: true
rate_limit_remaining: res.json.rate_limit.remaining
rate_limit_reset: res.json.rate_limit.reset_time
# Query Parameter API Key
- name: Test API Key in Query
action: http
with:
url: "{{vars.api_base_url}}/data?api_key={{vars.api_key}}"
test: res.status == 200
outputs:
query_auth_success: true
# Test Rate Limiting
- name: Test Rate Limit Info
action: http
with:
url: "{{vars.api_base_url}}/rate-limit-status"
headers:
X-API-Key: "{{vars.api_key}}"
test: |
res.status == 200 &&
res.json.tier == "{{vars.rate_limit_tier}}" &&
res.json.requests_remaining > 0
outputs:
requests_remaining: res.json.requests_remaining
requests_per_hour: res.json.limits.per_hour
current_usage: res.json.current_usage
# Test Invalid API Key
- name: Test Invalid API Key
action: http
with:
url: "{{vars.api_base_url}}/data"
headers:
X-API-Key: "invalid_key_123"
test: res.status == 401 || res.status == 403
outputs:
invalid_key_rejected: res.status == 401 || res.status == 403
- name: API Key Test Summary
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
yaml
name: JSON Response Validation
description: Validate API responses against expected schemas
env:
API_BASE_URL: https://api.yourservice.com
jobs:
response-validation:
name: Response Schema Validation
steps:
- name: Get User List
id: users
action: http
with:
url: "{{vars.api_base_url}}/users"
headers:
Authorization: "Bearer {{vars.api_token}}"
test: |
res.status == 200 &&
res.json != null &&
res.json.data != null &&
res.json.data.length > 0 &&
# Validate response structure
res.json.meta != null &&
res.json.meta.total != null &&
res.json.meta.page != null &&
res.json.meta.per_page != null &&
# Validate first user object
res.json.data[0].id != null &&
res.json.data[0].email != null &&
res.json.data[0].name != null &&
res.json.data[0].created_at != null &&
# Validate data types
typeof(res.json.data[0].id) == "number" &&
typeof(res.json.data[0].email) == "string" &&
typeof(res.json.data[0].active) == "boolean"
outputs:
total_users: res.json.meta.total
users_per_page: res.json.meta.per_page
first_user_id: res.json.data[0].id
first_user_email: res.json.data[0].email
- name: Get Single User Details
action: http
with:
url: "{{vars.api_base_url}}/users/{{outputs.users.first_user_id}}"
headers:
Authorization: "Bearer {{vars.api_token}}"
test: |
res.status == 200 &&
res.json.user != null &&
# Validate required fields
res.json.user.id == outputs.users.first_user_id &&
res.json.user.email == "{{outputs.users.first_user_email}}" &&
res.json.user.profile != null &&
# Validate nested objects
res.json.user.profile.first_name != null &&
res.json.user.profile.last_name != null &&
res.json.user.preferences != null &&
# Validate arrays
res.json.user.roles != null &&
res.json.user.roles.length > 0 &&
res.json.user.permissions != null
outputs:
user_roles: res.json.user.roles
user_permissions_count: res.json.user.permissions.length
profile_complete: res.json.user.profile.first_name != null && res.json.user.profile.last_name != null
- name: Test Data Consistency
action: http
with:
url: "{{vars.api_base_url}}/users/{{outputs.users.first_user_id}}/orders"
headers:
Authorization: "Bearer {{vars.api_token}}"
test: |
res.status == 200 &&
res.json.orders != null &&
# Validate all orders belong to the user
res.json.orders.all(order -> order.user_id == outputs.users.first_user_id) &&
# Validate order structure
res.json.orders.all(order ->
order.id != null &&
order.total != null &&
order.status != null &&
order.created_at != null
) &&
# Validate business logic
res.json.orders.all(order -> order.total >= 0) &&
res.json.orders.filter(order -> order.status == "completed").all(order -> order.completed_at != null)
outputs:
order_count: res.json.orders.length
completed_orders: res.json.orders.filter(order -> order.status == "completed").length
total_spent: res.json.orders.filter(order -> order.status == "completed").map(order -> order.total).sum()
- name: Validation Summary
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: {{outputs.user_roles.length}} 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
yaml
name: Error Response Validation
description: Test error handling and response formats
env:
API_BASE_URL: https://api.yourservice.com
jobs:
error-response-tests:
name: Error Response Tests
steps:
# Test 400 - Bad Request
- name: Test Bad Request
action: 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.status == 400 &&
res.json.error != null &&
res.json.error.code == "validation_error" &&
res.json.error.message != null &&
res.json.error.details != null &&
res.json.error.details.length > 0
outputs:
validation_errors: res.json.error.details
error_code: res.json.error.code
# Test 401 - Unauthorized
- name: Test Unauthorized Access
action: http
with:
url: "{{vars.api_base_url}}/admin/users"
test: |
res.status == 401 &&
res.json.error != null &&
res.json.error.code == "unauthorized" &&
res.json.error.message.contains("authentication")
outputs:
auth_error_proper: true
# Test 403 - Forbidden
- name: Test Forbidden Access
action: http
with:
url: "{{vars.api_base_url}}/admin/users"
headers:
Authorization: "Bearer {{env.USER_TOKEN}}" # Non-admin token
test: |
res.status == 403 &&
res.json.error != null &&
res.json.error.code == "forbidden" &&
res.json.error.message.contains("permission")
outputs:
permission_error_proper: true
# Test 404 - Not Found
- name: Test Not Found
action: http
with:
url: "{{vars.api_base_url}}/users/99999999"
headers:
Authorization: "Bearer {{vars.api_token}}"
test: |
res.status == 404 &&
res.json.error != null &&
res.json.error.code == "not_found" &&
res.json.error.resource == "user"
outputs:
not_found_error_proper: true
# Test 422 - Unprocessable Entity
- name: Test Unprocessable Entity
action: 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.status == 422 &&
res.json.error != null &&
res.json.error.code == "unprocessable_entity" &&
res.json.error.details != null
outputs:
duplicate_email_handled: true
# Test Rate Limiting - 429
- name: Test Rate Limiting
action: http
with:
url: "{{vars.api_base_url}}/high-rate-endpoint"
headers:
Authorization: "Bearer {{env.LIMITED_TOKEN}}"
test: |
res.status in [200, 429] &&
(res.status == 429 ?
res.json.error.code == "rate_limit_exceeded" &&
res.headers["retry-after"] != null :
true
)
continue_on_error: true
outputs:
rate_limiting_works: res.status == 429
- name: Error Handling Summary
echo: |
🚨 Error Response Validation Results:
400 Bad Request: ✅ Proper validation error format
Error Code: {{outputs.error_code}}
Validation Issues: {{outputs.validation_errors.length}}
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
yaml
name: E-commerce Workflow Testing
description: Test complete e-commerce user workflow
env:
API_BASE_URL: https://api.ecommerce.com
TEST_PRODUCT_ID: 123
TEST_USER_EMAIL: test@example.com
jobs:
user-registration-flow:
name: User Registration Workflow
steps:
- name: Register New User
id: register
action: 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.status == 201 &&
res.json.user.id != null &&
res.json.user.email != null &&
res.json.access_token != null
outputs:
user_id: res.json.user.id
user_email: res.json.user.email
access_token: res.json.access_token
- name: Email Verification Check
action: http
with:
url: "{{vars.api_base_url}}/user/profile"
headers:
Authorization: "Bearer {{outputs.register.access_token}}"
test: |
res.status == 200 &&
res.json.email_verified == false &&
res.json.verification_email_sent == true
outputs:
verification_pending: true
shopping-flow:
name: Shopping Workflow
needs: [user-registration-flow]
steps:
- name: Browse Products
id: browse
action: http
with:
url: "{{vars.api_base_url}}/products?category=electronics&limit=10"
test: |
res.status == 200 &&
res.json.products.length > 0 &&
res.json.products[0].id != null &&
res.json.products[0].price > 0
outputs:
available_products: res.json.products.length
first_product_id: res.json.products[0].id
first_product_price: res.json.products[0].price
- name: Add to Cart
id: add-cart
action: 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.status == 201 &&
res.json.cart_item.id != null &&
res.json.cart_item.quantity == 2 &&
res.json.cart_total > 0
outputs:
cart_item_id: res.json.cart_item.id
cart_total: res.json.cart_total
- name: View Cart
action: http
with:
url: "{{vars.api_base_url}}/cart"
headers:
Authorization: "Bearer {{outputs.user-registration-flow.access_token}}"
test: |
res.status == 200 &&
res.json.items.length == 1 &&
res.json.items[0].product_id == outputs.browse.first_product_id &&
res.json.total == outputs.add-cart.cart_total
outputs:
cart_verified: true
checkout-flow:
name: Checkout Workflow
needs: [shopping-flow]
steps:
- name: Apply Discount Code
id: discount
action: 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.status == 200 &&
res.json.discount_applied == true &&
res.json.discount_amount > 0 &&
res.json.new_total < outputs.shopping-flow.cart_total
continue_on_error: true
outputs:
discount_applied: res.status == 200
discount_amount: res.json.discount_amount
final_total: res.json.new_total
- name: Add Payment Method
id: payment
action: 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.status == 201 &&
res.json.payment_method.id != null &&
res.json.payment_method.last_four == "1111"
outputs:
payment_method_id: res.json.payment_method.id
- name: Create Order
id: order
action: 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.status == 201 &&
res.json.order.id != null &&
res.json.order.status == "processing" &&
res.json.order.total > 0
outputs:
order_id: res.json.order.id
order_status: res.json.order.status
order_total: res.json.order.total
- name: Verify Order Processing
action: http
with:
url: "{{vars.api_base_url}}/orders/{{outputs.order.order_id}}"
headers:
Authorization: "Bearer {{outputs.user-registration-flow.access_token}}"
test: |
res.status == 200 &&
res.json.order.id == outputs.order.order_id &&
res.json.order.user_id == outputs.user-registration-flow.user_id &&
res.json.order.items.length > 0
outputs:
order_verified: true
workflow-summary:
name: Workflow Test Summary
needs: [user-registration-flow, shopping-flow, checkout-flow]
steps:
- name: Complete Workflow Results
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
yaml
name: API Performance Testing
description: Test API response times and performance characteristics
env:
API_BASE_URL: https://api.yourservice.com
PERFORMANCE_THRESHOLD_MS: 1000
ACCEPTABLE_THRESHOLD_MS: 2000
jobs:
response-time-tests:
name: Response Time Performance Tests
steps:
- name: Lightweight Endpoint Test
id: ping
action: http
with:
url: "{{vars.api_base_url}}/ping"
test: |
res.status == 200 &&
res.time < 500
outputs:
ping_time: res.time
ping_fast: res.time < 200
- name: Database Query Test
id: db-query
action: http
with:
url: "{{vars.api_base_url}}/users?limit=100"
headers:
Authorization: "Bearer {{vars.api_token}}"
test: |
res.status == 200 &&
res.time < {{env.PERFORMANCE_THRESHOLD_MS}}
outputs:
query_time: res.time
query_performance: |
{{res.time < 500 ? "excellent" :
res.time < 1000 ? "good" :
res.time < 2000 ? "acceptable" : "poor"}}
- name: Complex Aggregation Test
id: aggregation
action: http
with:
url: "{{vars.api_base_url}}/analytics/summary"
headers:
Authorization: "Bearer {{vars.api_token}}"
test: |
res.status == 200 &&
res.time < {{env.ACCEPTABLE_THRESHOLD_MS}}
outputs:
aggregation_time: res.time
aggregation_acceptable: res.time < {{env.ACCEPTABLE_THRESHOLD_MS}}
- name: File Upload Test
id: upload
action: 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.status == 201 &&
res.time < 5000
outputs:
upload_time: res.time
upload_acceptable: res.time < 3000
- name: Performance Summary
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
yaml
# Good: Organized test structure
jobs:
authentication: # Group related tests
crud-operations: # Clear test categories
error-handling: # Logical organization
performance: # Separate concerns
2. Data Management
yaml
# 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
action: http
with:
url: "{{vars.api_base_url}}/users/{{outputs.create.user_id}}"
method: DELETE
3. Comprehensive Validation
yaml
# Good: Validate multiple aspects
test: |
res.status == 200 && # HTTP status
res.headers["content-type"].contains("json") && # Content type
res.json.id != null && # Required fields
typeof(res.json.id) == "number" && # Data types
res.json.email.contains("@") && # Data format
res.time < 1000 # Performance
4. Error Handling
yaml
# Good: Test error scenarios
- name: Test Invalid Input
action: http
with:
body: '{"invalid": "data"}'
test: res.status == 400
continue_on_error: true
# Good: Validate error responses
test: |
res.status == 400 &&
res.json.error.code == "validation_error" &&
res.json.error.details.length > 0
What's Next?
Now that you can test APIs comprehensively, explore:
- Error Handling Strategies - Robust error handling patterns
- Performance Testing - Advanced load testing techniques
- Environment Management - Managing test configurations
API testing is crucial for reliable services. Use these patterns to build comprehensive test suites that catch issues before they reach production.