π Introduction
API testing is fundamental in modern software development, especially in microservices architectures and distributed applications. A robust API testing framework allows us to validate functionality, performance, and security of our endpoints before reaching production.
In this article, we'll explore how to implement a complete API testing framework using Postman and Newman, automating it with GitHub Actions and creating a practical demonstration project.
π§ Tools and Setup
Newman Installation
Newman is the command-line version of Postman, perfect for CI/CD integration:
npm install -g newman
npm install -g newman-reporter-html
Verify installation:
newman --version
Project Configuration
Testing project structure:
api-testing-framework/
βββ collections/
β βββ user-api-tests.postman_collection.json
βββ environments/
β βββ dev.postman_environment.json
β βββ prod.postman_environment.json
βββ tests/
β βββ integration/
βββ reports/
βββ .github/workflows/
β βββ api-tests.yml
βββ package.json
π― Practical Implementation
- Demo API We create a simple REST API to demonstrate testing:
// server.js - Simple users API
const express = require('express');
const app = express();
app.use(express.json());
let users = [
{ id: 1, name: "John Doe", email: "john@example.com" },
{ id: 2, name: "Jane Smith", email: "jane@example.com" }
];
// GET /users - List users
app.get('/users', (req, res) => {
res.json({ users, total: users.length });
});
// POST /users - Create user
app.post('/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Name and email are required' });
}
const newUser = {
id: users.length + 1,
name,
email
};
users.push(newUser);
res.status(201).json(newUser);
});
// GET /users/:id - Get specific user
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
app.listen(3000, () => {
console.log('API running on port 3000');
});
- Postman Test Collection Example of automated tests in Postman:
// Pre-request Script to generate dynamic data
pm.globals.set("randomName", pm.variables.replaceIn('{{$randomFirstName}} {{$randomLastName}}'));
pm.globals.set("randomEmail", pm.variables.replaceIn('{{$randomEmail}}'));
// Test Scripts - Automatic validations
pm.test("Status code is 200", function () {
pm.response.to.have.status(200);
});
pm.test("Response time is less than 500ms", function () {
pm.expect(pm.response.responseTime).to.be.below(500);
});
pm.test("Response contains users array", function () {
const jsonData = pm.response.json();
pm.expect(jsonData).to.have.property('users');
pm.expect(jsonData.users).to.be.an('array');
});
pm.test("User has required fields", function () {
const jsonData = pm.response.json();
if (jsonData.users && jsonData.users.length > 0) {
const user = jsonData.users[0];
pm.expect(user).to.have.property('id');
pm.expect(user).to.have.property('name');
pm.expect(user).to.have.property('email');
}
});
// JSON Schema validation
const schema = {
type: "object",
properties: {
users: {
type: "array",
items: {
type: "object",
properties: {
id: { type: "number" },
name: { type: "string" },
email: { type: "string", format: "email" }
},
required: ["id", "name", "email"]
}
},
total: { type: "number" }
},
required: ["users", "total"]
};
pm.test("Schema validation", function () {
pm.response.to.have.jsonSchema(schema);
});
- Environment Configuration
// environments/dev.postman_environment.json
{
"name": "Development",
"values": [
{
"key": "baseUrl",
"value": "http://localhost:3000",
"enabled": true
},
{
"key": "apiKey",
"value": "dev-api-key-123",
"enabled": true
}
]
}
π€ GitHub Actions Automation
# .github/workflows/api-tests.yml
name: API Testing Framework
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 8 * * *' # Run daily at 8 AM UTC
jobs:
api-tests:
runs-on: ubuntu-latest
services:
api:
image: node:16
ports:
- 3000:3000
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
- name: Install dependencies
run: |
npm install
npm install -g newman newman-reporter-html
- name: Start API server
run: |
npm start &
sleep 5 # Wait for server to start
- name: Run API Health Check
run: |
curl -f http://localhost:3000/users || exit 1
- name: Execute Postman Collection
run: |
newman run collections/user-api-tests.postman_collection.json \
-e environments/dev.postman_environment.json \
--reporters cli,html \
--reporter-html-export reports/api-test-report.html \
--bail
- name: Upload Test Reports
uses: actions/upload-artifact@v3
if: always()
with:
name: api-test-reports
path: reports/
- name: Performance Testing
run: |
newman run collections/user-api-tests.postman_collection.json \
-e environments/dev.postman_environment.json \
--iteration-count 50 \
--reporters cli,json \
--reporter-json-export reports/performance-report.json
- name: Security Tests
run: |
# Run additional security tests
newman run collections/security-tests.postman_collection.json \
-e environments/dev.postman_environment.json \
--reporters cli
π Reporting and Monitoring
Results Analysis Script
// scripts/analyze-results.js
const fs = require('fs');
const path = require('path');
function analyzeTestResults(reportPath) {
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
const summary = {
totalTests: report.run.stats.tests.total,
passedTests: report.run.stats.tests.total - report.run.stats.tests.failed,
failedTests: report.run.stats.tests.failed,
averageResponseTime: calculateAverageResponseTime(report.run.executions),
successRate: ((report.run.stats.tests.total - report.run.stats.tests.failed) / report.run.stats.tests.total * 100).toFixed(2)
};
console.log('π Test Results Summary:');
console.log(`β
Passed: ${summary.passedTests}/${summary.totalTests}`);
console.log(`β Failed: ${summary.failedTests}`);
console.log(`β‘ Avg Response Time: ${summary.averageResponseTime}ms`);
console.log(`π Success Rate: ${summary.successRate}%`);
if (summary.failedTests > 0) {
console.log('\nπ¨ Failed Tests:');
report.run.executions.forEach(execution => {
execution.assertions.forEach(assertion => {
if (assertion.error) {
console.log(`- ${assertion.assertion}: ${assertion.error.message}`);
}
});
});
}
return summary;
}
function calculateAverageResponseTime(executions) {
const totalTime = executions.reduce((sum, exec) => sum + exec.response.responseTime, 0);
return Math.round(totalTime / executions.length);
}
// Run analysis
if (process.argv[2]) {
analyzeTestResults(process.argv[2]);
}
π Demo Project
π GitHub Repository: API Testing Framework Demo
The repository includes:
- β Complete demo API
- β Configured Postman collections
- β CI/CD automation
- β Detailed HTML reports
- β Performance testing
- β Security validations
Running the Project Locally
# Clone the repository
git clone https://github.com/SebastianFuentesAvalos/api-testing-framework-demo.git
cd api-testing-framework-demo
# Install dependencies
npm install
# Start the API
npm start
# In another terminal, run the tests
newman run collections/user-api-tests.postman_collection.json \
-e environments/dev.postman_environment.json \
--reporters cli,html \
--reporter-html-export reports/test-report.html
πΉ Video Demonstration
A complete 5-minute demonstration showing:
- π― Testing framework configuration
- π§ͺ Automated test execution
- π Report analysis
- π€ CI/CD integration in action
π― Best Practices
- Test Organization
- Separate by functionality: Group tests by modules or features
- Use descriptive names: GET_Users_Should_Return_Valid_Schema
- Implement data-driven testing: Use CSV/JSON files for test data
- Robust Validations
// Complete API response validation
pm.test("Complete API Response Validation", function() {
const response = pm.response.json();
// Status and response time
pm.expect(pm.response.code).to.be.oneOf([200, 201, 202]);
pm.expect(pm.response.responseTime).to.be.below(1000);
// Security headers
pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');
pm.expect(pm.response.headers.get('X-Content-Type-Options')).to.eql('nosniff');
// Data structure
pm.expect(response).to.have.property('data');
pm.expect(response.data).to.be.an('array');
// Business logic validation
if (response.data.length > 0) {
response.data.forEach(item => {
pm.expect(item.id).to.be.a('number').and.above(0);
pm.expect(item.email).to.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
});
}
});
- Test Data Management
// Cleanup and setup test data
pm.test("Test Data Cleanup", function() {
// Clean up previous test data
pm.sendRequest({
url: pm.environment.get("baseUrl") + "/test-cleanup",
method: 'DELETE',
header: {
'Authorization': 'Bearer ' + pm.environment.get("testToken")
}
}, function(err, response) {
pm.expect(response.code).to.be.oneOf([200, 404]);
});
});
π§Ύ Conclusion
A well-implemented API testing framework is crucial for maintaining quality and reliability in modern applications. The combination of Postman and Newman provides a complete solution that spans from manual testing to full CI/CD automation.
Key benefits implemented:
- β Early error detection before production
- β Complete automation with scheduled execution
- β Detailed reports for analysis and decision making
- β Seamless integration with existing DevOps workflows
- β Scalability for teams of any size By integrating these practices into your development workflow, you'll significantly reduce production bugs and increase confidence in your releases.
This is the complete article in markdown format ready for Dev.to publication! The article includes all the necessary elements:
- Proper markdown formatting with headers (`##`, `###`)
- Code blocks with syntax highlighting
- Emojis for visual appeal
- Real-world examples
- Complete project structure
- GitHub repository reference
- Video demonstration section
- Best practices
- Tags for discoverability
You can copy this directly into Dev.to's editor or save it as a `.md` file.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.