DEV Community

angel923
angel923

Posted on

Applying API Testing Frameworks: Real-World Examples Introduction

API testing is fundamental in modern software development. With the proliferation of microservices architectures and distributed applications, ensuring our APIs function correctly is more critical than ever. In this article, we'll explore the main API testing frameworks with practical examples you can implement today.

Why is API Testing Crucial?
APIs act as the nervous system of modern applications. An API failure can:

Disrupt critical services
Affect user experience
Cause significant financial losses
Compromise data security
Main API Testing Frameworks

  1. Postman + Newman (JavaScript/Node.js) Postman is a popular tool that allows you to create, test, and document APIs. Newman is its command-line version.

Practical Example: E-commerce API Testing
javascript
// Example test in Postman
pm.test("Verify product is created correctly", function () {
const jsonData = pm.response.json();

// Verify status code
pm.response.to.have.status(201);

// Verify response structure
pm.expect(jsonData).to.have.property('id');
pm.expect(jsonData.name).to.eql(pm.environment.get("product_name"));
pm.expect(jsonData.price).to.be.above(0);

// Save ID for subsequent tests
pm.environment.set("product_id", jsonData.id);
Enter fullscreen mode Exit fullscreen mode

});

pm.test("Verify response time", function () {
pm.expect(pm.response.responseTime).to.be.below(2000);
});
Environment Configuration
json
{
"name": "E-commerce API Tests",
"values": [
{
"key": "base_url",
"value": "https://api.mystore.com/v1"
},
{
"key": "api_key",
"value": "{{$randomUUID}}"
}
]
}

  1. REST Assured (Java) REST Assured is a powerful framework for testing REST APIs in Java.

Practical Example: Banking System API Testing
java
import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import static io.restassured.RestAssured.;
import static org.hamcrest.Matchers.
;

public class BankingAPITest {

@BeforeClass
public void setup() {
    RestAssured.baseURI = "https://api.bank.com";
    RestAssured.basePath = "/v2";
}

@Test
public void testCreateAccount() {
    String requestBody = """
        {
            "customer_id": "12345",
            "account_type": "savings",
            "initial_deposit": 1000.00,
            "currency": "USD"
        }
        """;

    given()
        .header("Authorization", "Bearer " + getAuthToken())
        .header("Content-Type", "application/json")
        .body(requestBody)
    .when()
        .post("/accounts")
    .then()
        .statusCode(201)
        .body("account_number", notNullValue())
        .body("balance", equalTo(1000.00f))
        .body("status", equalTo("active"))
        .time(lessThan(3000L));
}

@Test
public void testGetAccountBalance() {
    String accountId = createTestAccount();

    given()
        .header("Authorization", "Bearer " + getAuthToken())
        .pathParam("accountId", accountId)
    .when()
        .get("/accounts/{accountId}/balance")
    .then()
        .statusCode(200)
        .body("account_id", equalTo(accountId))
        .body("available_balance", greaterThanOrEqualTo(0f))
        .body("currency", equalTo("USD"));
}

@Test
public void testTransferFunds() {
    String fromAccount = createTestAccount();
    String toAccount = createTestAccount();

    String transferRequest = String.format("""
        {
            "from_account": "%s",
            "to_account": "%s",
            "amount": 500.00,
            "description": "Test transfer"
        }
        """, fromAccount, toAccount);

    given()
        .header("Authorization", "Bearer " + getAuthToken())
        .header("Content-Type", "application/json")
        .body(transferRequest)
    .when()
        .post("/transfers")
    .then()
        .statusCode(200)
        .body("transaction_id", notNullValue())
        .body("status", equalTo("completed"))
        .body("amount", equalTo(500.00f));
}

private String getAuthToken() {
    // Implement authentication logic
    return "mock-jwt-token";
}

private String createTestAccount() {
    // Implement test account creation
    return "ACC-" + System.currentTimeMillis();
}
Enter fullscreen mode Exit fullscreen mode

}

  1. pytest + requests (Python) A powerful combination for API testing in Python.

Practical Example: Social Media API Testing
python
import pytest
import requests
import json
from datetime import datetime

class TestSocialMediaAPI:

@pytest.fixture(autouse=True)
def setup(self):
    self.base_url = "https://api.socialmedia.com/v1"
    self.headers = {
        "Authorization": "Bearer test-token",
        "Content-Type": "application/json"
    }
    self.test_user_id = None

def test_create_user(self):
    """Test creating a new user"""
    user_data = {
        "username": f"testuser_{int(datetime.now().timestamp())}",
        "email": "test@example.com",
        "password": "SecurePass123!",
        "profile": {
            "first_name": "Test",
            "last_name": "User",
            "bio": "Test user for API testing"
        }
    }

    response = requests.post(
        f"{self.base_url}/users",
        headers=self.headers,
        json=user_data
    )

    assert response.status_code == 201

    response_data = response.json()
    assert "user_id" in response_data
    assert response_data["username"] == user_data["username"]
    assert response_data["email"] == user_data["email"]
    assert "password" not in response_data  # Verify password is not exposed

    self.test_user_id = response_data["user_id"]

def test_create_post(self):
    """Test creating a new post"""
    if not self.test_user_id:
        self.test_create_user()

    post_data = {
        "user_id": self.test_user_id,
        "content": "This is a test post for API testing",
        "tags": ["testing", "api", "automation"],
        "visibility": "public"
    }

    response = requests.post(
        f"{self.base_url}/posts",
        headers=self.headers,
        json=post_data
    )

    assert response.status_code == 201
    assert response.headers.get("Content-Type") == "application/json"

    post_response = response.json()
    assert post_response["content"] == post_data["content"]
    assert post_response["user_id"] == self.test_user_id
    assert isinstance(post_response["created_at"], str)
    assert len(post_response["tags"]) == 3

def test_get_user_feed(self):
    """Test getting user feed"""
    response = requests.get(
        f"{self.base_url}/users/{self.test_user_id}/feed",
        headers=self.headers,
        params={"limit": 10, "offset": 0}
    )

    assert response.status_code == 200

    feed_data = response.json()
    assert "posts" in feed_data
    assert "total_count" in feed_data
    assert "has_more" in feed_data
    assert isinstance(feed_data["posts"], list)

def test_api_performance(self):
    """Test API performance"""
    import time

    start_time = time.time()
    response = requests.get(
        f"{self.base_url}/posts/trending",
        headers=self.headers
    )
    end_time = time.time()

    response_time = (end_time - start_time) * 1000  # in milliseconds

    assert response.status_code == 200
    assert response_time < 2000  # Less than 2 seconds

def test_error_handling(self):
    """Test error handling"""
    # Test with invalid user ID
    response = requests.get(
        f"{self.base_url}/users/invalid-id",
        headers=self.headers
    )

    assert response.status_code == 404

    error_data = response.json()
    assert "error" in error_data
    assert "message" in error_data

@pytest.fixture(scope="session", autouse=True)
def cleanup(self):
    """Clean up test data after tests"""
    yield
    if self.test_user_id:
        requests.delete(
            f"{self.base_url}/users/{self.test_user_id}",
            headers=self.headers
        )
Enter fullscreen mode Exit fullscreen mode
  1. Cypress for APIs (JavaScript) Although Cypress is known for E2E testing, it's also excellent for API testing.

Practical Example: Task Management API Testing
javascript
// cypress/integration/task-api.spec.js
describe('Task Management API Tests', () => {
let authToken;
let projectId;
let taskId;

before(() => {
    // Authentication
    cy.request({
        method: 'POST',
        url: 'https://api.taskmanager.com/v1/auth/login',
        body: {
            email: 'test@example.com',
            password: 'testpassword'
        }
    }).then((response) => {
        authToken = response.body.access_token;
    });
});

it('Should create a new project', () => {
    cy.request({
        method: 'POST',
        url: 'https://api.taskmanager.com/v1/projects',
        headers: {
            'Authorization': `Bearer ${authToken}`,
            'Content-Type': 'application/json'
        },
        body: {
            name: 'Test Project',
            description: 'Project created for API testing',
            deadline: '2024-12-31',
            priority: 'high'
        }
    }).then((response) => {
        expect(response.status).to.eq(201);
        expect(response.body).to.have.property('project_id');
        expect(response.body.name).to.eq('Test Project');
        expect(response.body.status).to.eq('active');

        projectId = response.body.project_id;
    });
});

it('Should create a task within the project', () => {
    cy.request({
        method: 'POST',
        url: `https://api.taskmanager.com/v1/projects/${projectId}/tasks`,
        headers: {
            'Authorization': `Bearer ${authToken}`,
            'Content-Type': 'application/json'
        },
        body: {
            title: 'Implement API testing',
            description: 'Create automated tests for the API',
            assignee: 'test@example.com',
            due_date: '2024-12-15',
            priority: 'medium',
            labels: ['testing', 'api', 'automation']
        }
    }).then((response) => {
        expect(response.status).to.eq(201);
        expect(response.body.title).to.eq('Implement API testing');
        expect(response.body.status).to.eq('pending');
        expect(response.body.labels).to.have.length(3);

        taskId = response.body.task_id;
    });
});

it('Should update task status', () => {
    cy.request({
        method: 'PATCH',
        url: `https://api.taskmanager.com/v1/tasks/${taskId}`,
        headers: {
            'Authorization': `Bearer ${authToken}`,
            'Content-Type': 'application/json'
        },
        body: {
            status: 'in_progress',
            progress_percentage: 25
        }
    }).then((response) => {
        expect(response.status).to.eq(200);
        expect(response.body.status).to.eq('in_progress');
        expect(response.body.progress_percentage).to.eq(25);
    });
});

it('Should get project analytics', () => {
    cy.request({
        method: 'GET',
        url: `https://api.taskmanager.com/v1/projects/${projectId}/analytics`,
        headers: {
            'Authorization': `Bearer ${authToken}`
        }
    }).then((response) => {
        expect(response.status).to.eq(200);
        expect(response.body).to.have.property('total_tasks');
        expect(response.body).to.have.property('completed_tasks');
        expect(response.body).to.have.property('pending_tasks');
        expect(response.body).to.have.property('completion_rate');

        // Validate fast response
        expect(response.duration).to.be.lessThan(3000);
    });
});
Enter fullscreen mode Exit fullscreen mode

});
Best Practices for API Testing

  1. Test Structure Arrange: Set up test data Act: Execute the action Assert: Verify results
  2. Test Data Management
    python

    Example of test data factory

    class TestDataFactory:
    @staticmethod
    def create_user_data():
    return {
    "username": f"user_{uuid.uuid4().hex[:8]}",
    "email": f"test_{uuid.uuid4().hex[:8]}@example.com",
    "password": "SecurePass123!"
    }

    @staticmethod
    def create_product_data():
    return {
    "name": f"Test Product {random.randint(1, 1000)}",
    "price": round(random.uniform(10.0, 1000.0), 2),
    "category": random.choice(["electronics", "clothing", "books"])
    }

  3. Testing Different Scenarios
    Happy Path: Normal use cases
    Edge Cases: Boundary conditions
    Error Handling: Error management
    Security Testing: Security validations

  4. Automation and CI/CD
    yaml

    Example GitHub Actions for API testing

    name: API Tests
    on: [push, pull_request]

jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install Newman
run: npm install -g newman
- name: Run API Tests
run: newman run postman_collection.json -e environment.json --reporters cli,json
Complementary Tools

  1. Test Data Generation Faker.js: For JavaScript Factory Boy: For Python JavaFaker: For Java
  2. Mocking and Stubbing WireMock: For simulating external APIs MockServer: For creating complex mocks Nock: For Node.js
  3. Monitoring and Reporting Allure: For detailed reports Newman HTML Reporter: For Postman pytest-html: For Python Advanced API Testing Techniques
  4. Contract Testing javascript // Example with Pact.js const { Pact } = require('@pact-foundation/pact');

const provider = new Pact({
consumer: 'UserService',
provider: 'ProductService',
port: 1234,
});

describe('Product API Contract Tests', () => {
beforeAll(() => provider.setup());

afterEach(() => provider.verify());

afterAll(() => provider.finalize());

it('should get product by ID', async () => {
await provider.addInteraction({
state: 'product with ID 1 exists',
uponReceiving: 'a request for product with ID 1',
withRequest: {
method: 'GET',
path: '/products/1',
headers: {
'Accept': 'application/json'
}
},
willRespondWith: {
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: {
id: 1,
name: 'Test Product',
price: 99.99
}
}
});

// Test implementation here
Enter fullscreen mode Exit fullscreen mode

});
});

  1. Load Testing javascript // Example with Artillery module.exports = { config: { target: 'https://api.example.com', phases: [ { duration: 60, arrivalRate: 10 }, { duration: 120, arrivalRate: 50 }, { duration: 60, arrivalRate: 10 } ] }, scenarios: [ { name: 'Get products', weight: 70, flow: [ { get: { url: '/products' } }, { think: 1 } ] }, { name: 'Create product', weight: 30, flow: [ { post: { url: '/products', json: { name: 'Test Product {{ $randomString() }}', price: '{{ $randomInt(10, 1000) }}' } } } ] } ] };
  2. Security Testing
    python

    Example security tests

    class TestAPISecurity:

    def test_sql_injection_protection(self):
    """Test SQL injection protection"""
    malicious_payload = "'; DROP TABLE users; --"

    response = requests.get(
        f"{self.base_url}/users",
        params={"search": malicious_payload},
        headers=self.headers
    )
    
    # Should not return 500 error or expose database errors
    assert response.status_code != 500
    assert "sql" not in response.text.lower()
    assert "database" not in response.text.lower()
    

    def test_xss_protection(self):
    """Test XSS protection"""
    xss_payload = "alert(&#39;XSS&#39;)"

    response = requests.post(
        f"{self.base_url}/posts",
        json={"content": xss_payload},
        headers=self.headers
    )
    
    if response.status_code == 201:
        # If creation succeeds, check if content is properly escaped
        post_data = response.json()
        assert "<script>" not in post_data["content"]
    

    def test_rate_limiting(self):
    """Test rate limiting"""
    responses = []

    for i in range(101):  # Attempt 101 requests
        response = requests.get(
            f"{self.base_url}/products",
            headers=self.headers
        )
        responses.append(response.status_code)
    
    # Should encounter rate limiting
    assert 429 in responses  # Too Many Requests
    

    Performance Monitoring in Tests
    python
    import time
    import statistics

class PerformanceTestMixin:

def measure_response_time(self, func, *args, **kwargs):
    """Measure response time of API calls"""
    times = []

    for _ in range(5):  # Run 5 times for average
        start = time.time()
        response = func(*args, **kwargs)
        end = time.time()

        times.append((end - start) * 1000)  # Convert to ms

    return {
        'min': min(times),
        'max': max(times),
        'avg': statistics.mean(times),
        'median': statistics.median(times)
    }

def test_performance_benchmarks(self):
    """Test performance benchmarks"""
    stats = self.measure_response_time(
        requests.get,
        f"{self.base_url}/products",
        headers=self.headers
    )

    assert stats['avg'] < 1000  # Average under 1 second
    assert stats['max'] < 2000  # Max under 2 seconds

    print(f"Performance Stats: {stats}")
Enter fullscreen mode Exit fullscreen mode

API Testing Checklist
Before Testing
API documentation reviewed
Test environment set up
Authentication configured
Test data prepared
During Testing
Status codes validated
Response structure verified
Data types checked
Error handling tested
Performance measured
Security aspects validated
After Testing
Test results documented
Issues reported
Regression tests created
CI/CD pipeline updated
Conclusions
API testing is a discipline that requires planning, appropriate tools, and best practices. The frameworks presented offer different approaches depending on your project's technology stack:

Postman/Newman: Ideal for teams needing visual tools and collaboration
REST Assured: Perfect for Java projects with robust testing
pytest + requests: Excellent for Python teams seeking flexibility
Cypress: Ideal when you need to combine API testing with E2E
The key to success lies in choosing the right tools for your context, implementing tests from the beginning of development, and maintaining a test suite that evolves with your API.

Additional Resources
REST Assured Official Documentation
Postman Testing Guide
pytest Documentation
Cypress API Testing Guide
API Testing Best Practices
Do you implement API testing in your projects? Share your experience in the comments and let's help create better APIs together.

Top comments (0)