API Testing with Dredd
Learn how to use Dredd for automated API contract testing against your OpenAPI specification
API Testing with Dredd
Overview
Dredd is a powerful tool for contract testing - it validates that your API implementation matches your OpenAPI specification. DataBridge includes automated Dredd setup for continuous API testing.
What is Contract Testing?
Contract testing ensures your API:
- ✅ Implements all endpoints in the OpenAPI spec
- ✅ Returns correct HTTP status codes
- ✅ Returns data in the expected format
- ✅ Validates request parameters properly
- ✅ Handles errors as documented
Quick Start
1. Setup Dredd
./scripts/setup-dredd.sh
This creates:
dredd.yml- Dredd configurationdredd-hooks.js- Custom test logictest-reports/- Output directory- npm scripts for testing
2. Start Your API
npm run dev
3. Run Tests
npm run test:api
Dredd will:
- Read your
openapi.json - Generate test requests for each endpoint
- Call your running API
- Validate responses match the spec
- Generate HTML & Markdown reports
Test Output
info: Beginning Dredd testing...
pass: GET /user (200) duration: 45ms
pass: POST /user (201) duration: 89ms
pass: GET /user/1 (200) duration: 32ms
pass: PUT /user/1 (200) duration: 67ms
pass: DELETE /user/1 (204) duration: 41ms
complete: 5 passing, 0 failing, 0 errors, 0 skipped
complete: Tests took 274ms
Configuration (dredd.yml)
The setup script creates a complete dredd.yml:
hookfiles: ./dredd-hooks.js # Custom test logic
language: javascript
server: npm start # Auto-start server
server-wait: 3 # Wait 3 seconds for startup
reporter: ['html', 'markdown'] # Output formats
output: ['./test-reports/dredd.html', './test-reports/dredd.md']
blueprint: ./src/openapi.json # Your OpenAPI spec
endpoint: 'http://localhost:3000' # API endpoint
details: true # Show detailed errors
Custom Hooks (dredd-hooks.js)
Hooks let you customize test behavior. The setup script creates examples:
Capture Created Resource IDs
const hooks = require('hooks');
const createdIds = {};
// After creating a user, save the ID
hooks.after('POST /user > 201 > application/json', (transaction, done) => {
const response = JSON.parse(transaction.real.body);
if (response.id) {
createdIds.user = response.id;
console.log(`✅ Created user with ID: ${response.id}`);
}
done();
});
Use Captured IDs in Later Tests
// Use the created ID to test GET /user/{id}
hooks.before('GET /user/{id} > 200 > application/json', (transaction, done) => {
if (createdIds.user) {
const url = transaction.fullPath.replace('{id}', createdIds.user);
transaction.fullPath = url;
transaction.request.uri = url;
}
done();
});
Clean Up After Tests
// Clean up created resources
hooks.after('DELETE /user/{id} > 204', (transaction, done) => {
console.log('🧹 Cleaned up test user');
delete createdIds.user;
done();
});
Skip Specific Tests
// Skip authentication tests in development
hooks.before('POST /auth/login > 200 > application/json', (transaction, done) => {
if (process.env.NODE_ENV === 'development') {
transaction.skip = true;
}
done();
});
Add Custom Headers
// Add authorization header to all requests
hooks.beforeEach((transaction, done) => {
transaction.request.headers['Authorization'] = 'Bearer test-token';
done();
});
Test Reports
HTML Report
Open test-reports/dredd.html in browser:
- Summary - Pass/fail counts, duration
- Endpoint List - All tested endpoints
- Request/Response - Full details for each test
- Errors - Stack traces and expected vs actual
Markdown Report
Open test-reports/dredd.md:
# Dredd Tests
## Summary
- **Total:** 15 tests
- **Passing:** 14 ✅
- **Failing:** 1 ❌
- **Duration:** 1.2s
## Failing Tests
### POST /user > 201 > application/json
**Error:** Response body doesn't match schema
Expected:
```json
{ "id": 1, "name": "John", "email": "john@example.com" }
Actual:
{ "userId": 1, "name": "John", "email": "john@example.com" }
## npm Scripts
The setup adds these scripts to `package.json`:
### test:api
```bash
npm run test:api
Runs all contract tests with HTML/Markdown reports.
test:api:debug
npm run test:api:debug
Debug mode with verbose logging:
- Shows all HTTP requests/responses
- Displays hook execution
- Prints validation details
test:api:dry
npm run test:api:dry
Dry run - validates spec without making requests:
- Checks OpenAPI syntax
- Validates all endpoint definitions
- No API calls made
CI/CD Integration
GitHub Actions
name: API Contract Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm install
- name: Setup Dredd
run: ./scripts/setup-dredd.sh
- name: Run contract tests
run: npm run test:api
- name: Upload test reports
uses: actions/upload-artifact@v3
if: always()
with:
name: dredd-reports
path: test-reports/
GitLab CI
api-tests:
script:
- ./scripts/setup-dredd.sh
- npm run test:api
artifacts:
when: always
paths:
- test-reports/
reports:
junit: test-reports/dredd.xml
Advanced Usage
Test Specific Endpoints Only
# Test only User endpoints
dredd --names --grep "/user" dredd.yml
# Test only POST requests
dredd --names --grep "POST" dredd.yml
Custom Server Command
In dredd.yml:
server: npm run start:test # Use test environment
Multiple Environments
# dredd-staging.yml
endpoint: 'https://staging.api.example.com'
# dredd-production.yml
endpoint: 'https://api.example.com'
Run against staging:
dredd dredd-staging.yml
Database Seeding
In dredd-hooks.js:
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
hooks.beforeAll(async (transactions, done) => {
// Seed test data
await prisma.user.create({
data: {
id: 1,
name: 'Test User',
email: 'test@example.com'
}
});
done();
});
hooks.afterAll(async (transactions, done) => {
// Clean up test data
await prisma.user.deleteMany();
await prisma.$disconnect();
done();
});
Troubleshooting
All Tests Failing
Check server is running:
curl http://localhost:3000/health
Check spec is valid:
./scripts/validate-openapi.sh
Run in debug mode:
npm run test:api:debug
Specific Endpoint Failing
Check the error in report:
cat test-reports/dredd.md
Test manually with curl:
curl -X GET http://localhost:3000/user
Compare with OpenAPI spec:
cat src/openapi.json | jq '.paths."/user".get'
Hook Not Executing
Check hook syntax:
// Correct format
hooks.before('POST /user > 201 > application/json', (transaction, done) => {
// Your code
done(); // Always call done()
});
Enable hook debugging:
dredd --loglevel=debug
Tests Taking Too Long
Increase timeout:
In dredd.yml:
hooks-worker-timeout: 10000 # 10 seconds
Reduce test scope:
dredd --names --grep "/user" dredd.yml # Test only users
Best Practices
1. Run Tests on Every Commit
Add to pre-commit hook:
#!/bin/bash
npm run test:api || {
echo "❌ API contract tests failed"
exit 1
}
2. Separate Test Database
Use .env.test:
DATABASE_URL="mysql://user:pass@localhost:3306/test_db"
Start with test env:
NODE_ENV=test npm run test:api
3. Clean Data Between Tests
hooks.beforeEach(async (transaction, done) => {
await prisma.user.deleteMany();
done();
});
4. Mock External Services
hooks.beforeAll((transactions, done) => {
// Mock external API
nock('https://external-api.com')
.get('/data')
.reply(200, { mock: 'data' });
done();
});
5. Version Your Spec
Commit openapi.json to git:
git add src/openapi.json
git commit -m "Update API spec"
Next Steps
- Mock Servers with Prism - Frontend development
- Postman Integration - Team testing
- Multi-Language SDKs - Client generation
Resources
Need help? Open an issue on GitHub
Was this page helpful?
Thank you for your feedback!