Hoverfly: Capture and Simulate API Traffic for Testing
One of the hardest parts of building integration tests is creating realistic test data. You can write mock responses by hand, but they drift from reality. You can call the real API in tests, but that introduces network dependency and flakiness.
Hoverfly solves this with a record-and-replay approach. It runs as a proxy between your application and real APIs, records the actual traffic, and then replays that recorded traffic in simulations. Your tests run against traffic captured from reality.
What Is Hoverfly?
Hoverfly is an open-source API simulation tool developed by SpectoLabs. It operates as an HTTP/HTTPS proxy with two primary modes:
- Capture mode: Hoverfly sits between your application and real APIs, recording all request/response pairs as "simulations"
- Simulate mode: Hoverfly replays recorded simulations without making real network calls
Beyond basic record/replay, Hoverfly supports:
- Spy mode: Simulate known traffic, pass through unknown requests to real services
- Synthesize mode: Generate responses dynamically with middleware
- Modify mode: Intercept and alter real traffic in flight
Installation
Binary (macOS):
brew install hoverfly/tap/hoverflyBinary (Linux/Windows): Download from hoverfly.io. Two binaries are included:
hoverfly— the proxy serverhoverctl— CLI control tool
Docker:
docker pull spectolabs/hoverflyCore Concepts
Simulations
A simulation is a JSON file containing captured request/response pairs. Each entry includes:
- Request matcher (URL, method, headers, body)
- Response (status, headers, body)
Simulations can be exported, committed to version control, and shared across teams.
Admin API
Hoverfly exposes an admin API on port 8888. hoverctl is a CLI wrapper for this API. You can also call it directly for automation:
curl http://localhost:8888/api/v2/hoverfly/modeStarting Hoverfly
# Start Hoverfly (proxy on 8500, admin on 8888)
hoverfly
<span class="hljs-comment"># Or with custom ports
hoverfly -pp 9000 -ap 9090Start the control tool:
hoverctl startCheck status:
hoverctl statusCapturing API Traffic
Setting capture mode
hoverctl mode captureRouting traffic through Hoverfly
Configure your application or HTTP client to use Hoverfly as a proxy:
curl:
curl --proxy http://localhost:8500 https://api.example.com/usersPython requests:
import requests
proxies = {
'http': 'http://localhost:8500',
'https': 'http://localhost:8500',
}
response = requests.get('https://api.example.com/users', proxies=proxies, verify=False)Node.js (axios):
const axios = require('axios')
const HttpsProxyAgent = require('https-proxy-agent')
const agent = new HttpsProxyAgent('http://localhost:8500')
const response = await axios.get('https://api.example.com/users', {
httpsAgent: agent,
})Go:
proxyURL, _ := url.Parse("http://localhost:8500")
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: transport}
resp, err := client.Get("https://api.example.com/users")HTTPS and certificate handling
For HTTPS traffic, Hoverfly performs TLS termination. Your client needs to trust Hoverfly's certificate:
# Export Hoverfly's certificate
hoverctl certificate --<span class="hljs-built_in">export cert.pem
<span class="hljs-comment"># Configure your client to trust it
<span class="hljs-built_in">export SSL_CERT_FILE=cert.pem
<span class="hljs-comment"># or
<span class="hljs-built_in">export NODE_EXTRA_CA_CERTS=cert.pemAlternatively, disable certificate verification for test environments (never in production).
Exporting the simulation
After capturing traffic, export it:
hoverctl export simulation.jsonThe JSON file contains all recorded request/response pairs. Commit this to version control.
Replay in Simulate Mode
# Import a previously captured simulation
hoverctl import simulation.json
<span class="hljs-comment"># Switch to simulate mode
hoverctl mode simulate
<span class="hljs-comment"># Now requests are served from the simulation
curl --proxy http://localhost:8500 https://api.example.com/usersNo network calls are made. Hoverfly matches incoming requests against the simulation and returns stored responses.
Simulation Matching
Hoverfly uses matchers to determine which simulation entry responds to a request. The default is exact matching — the URL, method, and body must match exactly.
Glob matching
For flexible URL matching:
{
"request": {
"path": [
{
"matcher": "glob",
"value": "/users/*"
}
],
"method": [
{
"matcher": "exact",
"value": "GET"
}
]
},
"response": {
"status": 200,
"body": "{\"id\": 1, \"name\": \"Alice\"}"
}
}Regex matching
{
"request": {
"path": [
{
"matcher": "regex",
"value": "/users/[0-9]+"
}
]
}
}JSONPath matching
Match on specific fields in a JSON body:
{
"request": {
"body": [
{
"matcher": "jsonpath",
"value": "$.email"
}
]
}
}Middleware
Middleware transforms requests and responses at runtime. Write middleware as any executable that reads from stdin and writes to stdout.
Example: Add latency
#!/usr/bin/env python3
# middleware/add-latency.py
import sys
import json
import time
data = json.load(sys.stdin)
if data['response']['status'] == 200:
time.sleep(0.3) # Add 300ms latency
print(json.dumps(data))Make it executable and configure Hoverfly to use it:
chmod +x middleware/add-latency.py
hoverctl middleware --binary python3 --script middleware/add-latency.pyExample: Inject errors
#!/usr/bin/env python3
import sys
import json
import random
data = json.load(sys.stdin)
# 10% of requests get a 503
if random.random() < 0.1:
data['response']['status'] = 503
data['response']['body'] = json.dumps({'error': 'Service unavailable'})
print(json.dumps(data))This lets you test your application's resilience without modifying mock files or the real service.
Example: Dynamic timestamps
#!/usr/bin/env python3
import sys
import json
from datetime import datetime
data = json.load(sys.stdin)
body = data['response'].get('body', '')
try:
parsed = json.loads(body)
if isinstance(parsed, dict):
parsed['timestamp'] = datetime.utcnow().isoformat()
data['response']['body'] = json.dumps(parsed)
except (json.JSONDecodeError, TypeError):
pass
print(json.dumps(data))CI Integration
GitHub Actions
name: Integration Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Hoverfly
run: |
wget https://github.com/SpectoLabs/hoverfly/releases/download/v1.9.0/hoverfly_bundle_linux_amd64.zip
unzip hoverfly_bundle_linux_amd64.zip
chmod +x hoverfly hoverctl
sudo mv hoverfly hoverctl /usr/local/bin/
- name: Start Hoverfly in simulate mode
run: |
hoverfly &
sleep 2
hoverctl import simulations/api-simulation.json
hoverctl mode simulate
- name: Run tests
run: npm test
env:
HTTP_PROXY: http://localhost:8500
HTTPS_PROXY: http://localhost:8500
NODE_TLS_REJECT_UNAUTHORIZED: 0Docker Compose
version: '3.8'
services:
hoverfly:
image: spectolabs/hoverfly
ports:
- "8500:8500"
- "8888:8888"
volumes:
- ./simulations:/simulations
command: -webserver -import /simulations/api-simulation.json
app:
build: .
depends_on:
- hoverfly
environment:
- HTTP_PROXY=http://hoverfly:8500
- HTTPS_PROXY=http://hoverfly:8500
ports:
- "3000:3000"Spy Mode for Hybrid Testing
Spy mode lets you simulate responses for known requests while passing unknown requests through to the real service:
hoverctl mode spyThis is useful when:
- You're building new API integrations and want to capture traffic for the new endpoints while simulating existing ones
- Your simulation doesn't have complete coverage and you want a safety net
Diff Mode for Regression Testing
Diff mode compares responses from your real API against a simulation, flagging discrepancies:
hoverctl mode diffMake requests with diff mode enabled, then check for differences:
hoverctl diff getThis helps detect when an external API has changed in ways that break your simulation assumptions. Run it periodically in CI to catch API drift before it causes test failures.
Managing Simulations with hoverctl
# List all imported simulations
hoverctl simulations
<span class="hljs-comment"># Delete specific simulation
hoverctl simulations --delete
<span class="hljs-comment"># View current mode
hoverctl mode
<span class="hljs-comment"># Check logs
hoverctl logsWhen to Use Hoverfly
Best use cases:
- Capturing complex third-party API responses — When the response schema is complex (Stripe, Twilio, AWS services), capturing real responses is faster than hand-writing them.
- Regression testing external API behavior — Use diff mode to detect when external APIs change.
- Testing across languages — Hoverfly is language-agnostic. Any application that can use an HTTP proxy works with it, regardless of language or framework.
- Network simulation — Add latency, packet loss, and error injection through middleware without modifying application code.
- Compliance and audit environments — Simulations serve as documentation of what external APIs your system calls and what responses it expects.
Less suitable for:
- Unit tests (too much overhead for per-unit isolation)
- Browser-level mocking (MSW is better suited)
- Tests that need fine-grained per-test response control (Mountebank or MSW handle this better)
Conclusion
Hoverfly's capture-then-simulate workflow solves a real problem: creating realistic test fixtures without maintaining them manually. By recording actual API traffic and replaying it, your tests run against responses that match what the real API returns.
The middleware system adds flexibility — you can inject errors, add latency, and transform responses at runtime without editing simulation files. This makes Hoverfly a strong choice for integration and contract testing, especially for teams working with complex third-party APIs.
Start by capturing a session against your real dependencies, commit the simulation JSON, and run it in CI. Your integration tests gain realistic data and network independence in a single step.