API testing is a critical component of modern software development, ensuring that your application programming interfaces (APIs) function correctly, perform well, and remain secure. In this article, we'll explore practical implementations of popular API testing frameworks with real-world code examples, inspired by insights from Top 10 API Testing Tools for REST & SOAP Services.
1. Postman (Collection Runner + Newman)
Use Case: Testing a RESTful e-commerce API
// postman_test_example.js
const newman = require('newman');
newman.run({
collection: require('./ecommerce-api-tests.json'),
reporters: 'cli',
iterationCount: 1
}, function (err) {
if (err) { throw err; }
console.log('Postman collection run complete!');
});
// Example test in Postman collection
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});
pm.test("Response time is less than 200ms", function () {
pm.expect(pm.response.responseTime).to.be.below(200);
});
pm.test("Product has valid structure", function () {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property('id');
pm.expect(jsonData).to.have.property('name');
pm.expect(jsonData).to.have.property('price');
pm.expect(jsonData.price).to.be.a('number');
});
Real-world application: Run regression tests in CI/CD pipeline using Newman (Postman's CLI tool)
2. RestAssured (Java)
Use Case: Testing a banking API with authentication
// BankingAPITest.java
import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.*;
public class BankingAPITest {
@Test
public void testAccountBalanceEndpoint() {
RestAssured.baseURI = "https://api.bank.example.com";
given()
.auth()
.oauth2("valid-access-token")
.pathParam("accountId", "12345")
.when()
.get("/accounts/{accountId}/balance")
.then()
.statusCode(200)
.body("accountId", equalTo("12345"))
.body("balance", is(not(empty())))
.body("currency", equalTo("USD"))
.time(lessThan(1000L));
}
@Test
public void testTransactionHistory() {
Response response = given()
.auth()
.basic("username", "password")
.queryParam("from", "2023-01-01")
.queryParam("to", "2023-12-31")
.get("/transactions");
response.then()
.statusCode(200)
.body("transactions", hasSize(greaterThan(0)))
.body("transactions[0].amount", is(notNullValue()));
}
}
Real-world application: Comprehensive testing of financial APIs with complex authentication requirements
3. PyTest with Requests (Python)
Use Case: Testing a weather API with parameterized tests
# test_weather_api.py
import pytest
import requests
BASE_URL = "https://api.weather.example.com/v1"
@pytest.mark.parametrize("city,country,expected_status", [
("London", "UK", 200),
("New York", "US", 200),
("Invalid City", "XX", 404),
])
def test_get_weather_by_city(city, country, expected_status):
response = requests.get(
f"{BASE_URL}/weather",
params={"city": city, "country": country},
headers={"Authorization": "Bearer test-api-key"}
)
assert response.status_code == expected_status
if expected_status == 200:
data = response.json()
assert "temperature" in data
assert "humidity" in data
assert "conditions" in data
assert isinstance(data["temperature"], float)
@pytest.fixture
def auth_token():
# Get auth token once for multiple tests
response = requests.post(
f"{BASE_URL}/auth",
json={"username": "testuser", "password": "testpass"}
)
return response.json()["token"]
def test_forecast_endpoint(auth_token):
response = requests.get(
f"{BASE_URL}/forecast",
params={"days": 5},
headers={"Authorization": f"Bearer {auth_token}"}
)
assert response.status_code == 200
forecast = response.json()
assert len(forecast) == 5
assert all("date" in day and "high" in day for day in forecast)
Real-world application: Data-driven testing of weather API with different locations and authentication scenarios
4. Karate DSL (BDD Style)
Use Case: Testing a microservices architecture with complex workflows
# user_management.feature
Feature: User Management API Tests
Background:
* url 'https://api.users.example.com'
* header Authorization = 'Bearer ' + token
* def token = call read('get_token.js') { username: 'admin', password: 'admin123' }
Scenario: Create and verify new user
Given path '/users'
And request { name: 'Test User', email: 'test.user@example.com', role: 'member' }
When method post
Then status 201
And match response == { id: '#number', name: 'Test User', email: 'test.user@example.com', role: 'member' }
* def userId = response.id
Given path '/users/' + userId
When method get
Then status 200
And match response contains { id: '#number', name: 'Test User' }
Scenario: Test user search with parameters
Given path '/users/search'
And param role = 'admin'
When method get
Then status 200
And match each response.users contains { role: 'admin' }
And assert response.users.length > 0
Real-world application: Testing complex user flows in microservices with readable BDD syntax
5. SoapUI (SOAP Service Testing)
Use Case: Testing a legacy SOAP-based inventory system
<!-- inventory_check.xml -->
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:inv="http://www.example.com/inventory">
<soapenv:Header/>
<soapenv:Body>
<inv:CheckInventoryRequest>
<inv:productId>1001</inv:productId>
<inv:warehouse>NYC</inv:warehouse>
</inv:CheckInventoryRequest>
</soapenv:Body>
</soapenv:Envelope>
// Groovy Script Assertion in SoapUI
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context)
def holder = groovyUtils.getXmlHolder("CheckInventoryRequest#Response")
def availableQty = holder.getNodeValue("//*:availableQuantity")
def status = holder.getNodeValue("//*:status")
assert availableQty.toInteger() >= 0
assert status == "IN_STOCK" || status == "OUT_OF_STOCK"
// Performance check
assert context.responseTime < 500
Real-world application: Maintaining and testing legacy SOAP services with complex XML payloads
Best Practices for API Testing Frameworks
- Environment Management: Use config files to manage different environments
// config.js
module.exports = {
dev: {
baseUrl: 'https://dev.api.example.com',
apiKey: 'dev-key-123'
},
staging: {
baseUrl: 'https://staging.api.example.com',
apiKey: 'staging-key-456'
},
prod: {
baseUrl: 'https://api.example.com',
apiKey: process.env.PROD_API_KEY
}
};
- Test Data Management: Generate test data dynamically
# test_data.py
from faker import Faker
fake = Faker()
def generate_user_data():
return {
'name': fake.name(),
'email': fake.email(),
'address': fake.address(),
'phone': fake.phone_number()
}
- Reporting: Generate comprehensive test reports
// RestAssured with ExtentReports
@AfterSuite
public void tearDown() {
ExtentReports extent = new ExtentReports();
ExtentSparkReporter spark = new ExtentSparkReporter("target/Spark.html");
extent.attachReporter(spark);
extent.flush();
}
- Performance Checks: Always include performance assertions
// Postman
pm.test("Response time is acceptable", function () {
pm.expect(pm.response.responseTime).to.be.below(300);
});
- Security Testing: Include basic security checks
# pytest
def test_secure_headers(response):
assert 'X-Content-Type-Options' in response.headers
assert response.headers['X-Frame-Options'] == 'DENY'
assert 'Content-Security-Policy' in response.headers
Conclusion
Choosing the right API testing framework depends on your tech stack, team skills, and API complexity. Postman is excellent for collaborative manual testing, RestAssured integrates well with Java ecosystems, PyTest offers Python simplicity, Karate provides BDD readability, and SoapUI handles SOAP services effectively.
By implementing these real-world examples and following best practices, you can build a robust API testing strategy that catches bugs early, ensures performance standards, and maintains API reliability throughout your development lifecycle.
Remember to:
- Test for both happy paths and error cases
- Validate response schemas
- Include performance assertions
- Test security aspects
- Automate in your CI/CD pipeline
- Maintain your tests as your API evolves
Top comments (1)
good job!