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 configuration
  • dredd-hooks.js - Custom test logic
  • test-reports/ - Output directory
  • npm scripts for testing

2. Start Your API

npm run dev

3. Run Tests

npm run test:api

Dredd will:

  1. Read your openapi.json
  2. Generate test requests for each endpoint
  3. Call your running API
  4. Validate responses match the spec
  5. 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

Resources


Need help? Open an issue on GitHub

Was this page helpful?