ENE is an end-to-end (e2e) testing framework that uses Docker containers to spin up test environments and validate your applications through HTTP, database, and mocked service interactions.
- Installation
- Quick Start
- CLI Commands
- Command Options
- Test Suite Configuration
- Examples
- Advanced Usage
- Go 1.18+ - Required to build the CLI
- Docker - Must be running for test execution
- mongosh (optional) - Required only if using MongoDB migrations with JS files
# Clone the repository
git clone <repository-url>
cd ene
# Build the CLI
go build -o ene main.go
# Optional: Move to PATH
sudo mv ene /usr/local/bin/# Scaffold a new test suite with HTTP mock
ene scaffold-test my-first-test --tmpl=httpmock
# Scaffold with multiple templates
ene scaffold-test my-api-test --tmpl=mongo,http,httpmockThis creates a new directory under ./tests/my-first-test/ with a suite.yml file.
# Validate all test suites
ene dry-run --verbose
# Validate a specific test file
ene dry-run tests/my-first-test/suite.yml --verbose# Run all tests
ene
# Run specific test suite
ene --suite=my-first-test
# Run with verbose output
ene --suite=my-first-test --verbose
# Run tests in parallel
ene --parallel
# Generate HTML and JSON reports
ene --html=report.html --json=report.jsonWhen called with no subcommand, ene runs all e2e tests.
ene [flags]List all available test suites in the tests/ directory.
ene list-suitesOutput:
Available test suites:
user-api-tests
payment-service-tests
integration-tests
Clean up orphaned Docker resources (containers, networks) created by ENE tests.
ene cleanup [resource-type] [flags]Resource Types:
all- Clean all resource types (default)networks- Clean only Docker networkscontainers- Clean only Docker containers
Flags:
--dry-run- Show what would be removed without actually removing--force- Skip confirmation prompt--all- Include all resources, not just orphaned ones--older-than=<duration>- Only clean resources older than specified duration (e.g., 1h, 30m, 24h)--verbose- Show detailed information about discovered resources
Examples:
# Interactive cleanup (shows what will be removed and asks for confirmation)
ene cleanup
# Preview what would be removed (safe, no changes made)
ene cleanup --dry-run --verbose
# Force cleanup without confirmation
ene cleanup --force
# Clean only networks
ene cleanup networks --force
# Clean only containers
ene cleanup containers --force
# Clean resources older than 1 hour
ene cleanup --older-than=1h --force
# Clean resources older than 24 hours
ene cleanup --older-than=24h --force
# Include all resources (not just orphaned)
ene cleanup --all --force
# Verbose output for debugging
ene cleanup --verbose --dry-runUse Cases:
CI/CD Integration:
# GitLab CI
after_script:
- ene cleanup --older-than=30m --force
# GitHub Actions
- name: Cleanup Docker Resources
if: always()
run: ene cleanup --older-than=30m --force --verbosePeriodic Cleanup (Cron):
# Add to crontab for nightly cleanup
0 2 * * * /usr/local/bin/ene cleanup --older-than=24h --force >> /var/log/ene-cleanup.log 2>&1Manual Troubleshooting:
# Check what's orphaned
ene cleanup --dry-run --verbose
# Clean specific type
ene cleanup containers --force
# Clean everything
ene cleanup --forceHow It Works:
The cleanup command:
- Discovers orphaned Docker resources created by ENE (identified by
testcontainers-prefix) - Filters by age if
--older-thanis specified - Removes containers first, then networks (proper ordering prevents "active endpoints" errors)
- Reports what was cleaned and any errors encountered
Troubleshooting:
If you see "network has active endpoints" errors:
# Clean containers first, then networks
ene cleanup containers --force
ene cleanup networks --forceFor more details, see:
Display the ENE version information.
ene versionCreate a new test suite scaffold with predefined templates.
ene scaffold-test <name> [flags]Arguments:
<name>- Name of the test suite (required)
Flags:
--tmpl=<templates>- Comma-separated list of templates to include- Available templates:
mongo,http,httpmock - Default:
mongo,httpmock
- Available templates:
Examples:
# Create test with default templates
ene scaffold-test user-service-test
# Create test with only HTTP mock
ene scaffold-test api-test --tmpl=httpmock
# Create test with MongoDB and HTTP service
ene scaffold-test integration-test --tmpl=mongo,httpValidate test configuration without running containers. Useful for CI/CD validation and syntax checking.
ene dry-run [test-file] [flags]Arguments:
[test-file]- Optional path to specific test file to validate
Flags:
--verbose/-v- Enable detailed validation output--debug- Enable debug mode with extra information--base-dir=<path>- Base directory for tests (default: current directory)
Examples:
# Validate all test suites
ene dry-run --verbose
# Validate specific test file
ene dry-run tests/my-test/suite.yml
# Validate with debug information
ene dry-run --debug --verboseList all available test suites in the tests directory.
ene list-suites [flags]Flags:
--base-dir=<path>- Base directory for tests (default: current directory)
Example:
ene list-suitesOutput:
Available test suites:
api-tests
integration-tests
mock-tests
Display version information.
ene versionOutput:
ene version dev
commit: unknown
built: unknown
Generate shell completion scripts for bash, zsh, fish, or PowerShell.
ene completion <shell>Examples:
# Bash completion
ene completion bash > /etc/bash_completion.d/ene
# Zsh completion
ene completion zsh > "${fpath[1]}/_ene"
# Fish completion
ene completion fish > ~/.config/fish/completions/ene.fish
# PowerShell completion
ene completion powershell > ene.ps1| Flag | Type | Default | Description |
|---|---|---|---|
--verbose / -v |
bool | false | Enable detailed logs including container output and HTTP request/response details |
--pretty |
bool | true | Pretty print output with colors and formatting |
--debug |
bool | false | Enable debug mode with extra diagnostic information |
--parallel |
bool | false | Run test suites in parallel (faster but more resource-intensive) |
--suite=<names> |
string | "" | Run specific test suites (comma-separated), supports partial matching |
--html=<path> |
string | "" | Generate HTML report at specified path |
--json=<path> |
string | "" | Generate JSON report at specified path |
--base-dir=<path> |
string | "" | Base directory for tests (default: current directory) |
--cleanup-cache |
bool | false | Cleanup old cached Docker images to prevent bloat |
--help / -h |
bool | false | Show help information |
--version |
bool | false | Show version information |
When --verbose is enabled, HTTP tests will display detailed request and response information:
Request Details:
- HTTP Method (GET, POST, etc.)
- Full URL including query parameters
- All request headers
- Request body payload
Response Details:
- HTTP status code
- All response headers
- Response body content
Example Output:
=== HTTP Request ===
POST http://localhost:8080/api/users?include=profile
Headers:
Content-Type: application/json
Authorization: Bearer test-token
Body:
{"name":"John Doe","email":"john@example.com"}
====================
=== HTTP Response ===
Status: 201 Created
Headers:
Content-Type: application/json
X-Request-Id: abc123
Body:
{"id":1,"name":"John Doe","email":"john@example.com"}
=====================
Failure Messages:
When an HTTP test fails, the error message automatically includes full request and response details, regardless of whether verbose mode is enabled. This ensures you have all the information needed to debug the failure:
expected status code 200, got 404
=== Request Details ===
GET http://localhost:8080/api/users/999?include=profile
Headers:
Authorization: Bearer test-token
Accept: application/json
========================
=== Response Details ===
Status: 404 Not Found
Headers:
Content-Type: application/json
Body:
{"error":"User not found"}
=========================
This is particularly useful for:
- Debugging failing tests
- Understanding API behavior
- Verifying request/response formats
- Troubleshooting authentication issues
- Checking query parameter handling
The --suite flag supports multiple patterns:
# Exact match
ene --suite=api-tests
# Multiple suites
ene --suite=api-tests,mock-tests
# Partial match (prefix)
ene --suite=api-
# Partial match (suffix)
ene --suite=-tests
# Multiple patterns with partial matching
ene --suite=TestService_,_Function.
├── tests/
│ ├── my-test-suite/
│ │ ├── suite.yml # Test configuration (required)
│ │ ├── db.js # MongoDB migrations (optional)
│ │ ├── Dockerfile # Custom Dockerfile (optional)
│ │ └── .env # Environment variables (optional)
│ └── another-test/
│ └── suite.yml
└── ene # CLI binary
The suite.yml file defines the test suite configuration.
kind: e2e_test:v1
name: my-test-suite
# Fixtures: Reusable data values (both array and map formats work)
fixtures:
- content_type_inline: application/json; charset=utf-8
- api_key: test-key-12345
# Units: Services/containers to spin up
units:
# HTTP Mock Server
- name: mock-api
kind: httpmock
app_port: 8080
routes:
- path: /healthcheck
method: GET
response:
status: 200
body:
status: ok
headers:
Content-Type: "{{ content_type_inline }}"
# MongoDB Database
- name: mongodb
kind: mongo
image: mongo:6.0
app_port: 27017
migrations: db.js
startup_timeout: 30s
# HTTP Service (your application)
- name: app
kind: http
dockerfile: Dockerfile
app_port: 8080
healthcheck: /health
startup_timeout: 4m
env_file: .env
env:
- DB_DSN={{ mongodb.dsn }}
- API_KEY={{ api_key }}
# Target: Which unit to send test requests to
target: app
# Tests: Test cases to execute
tests:
- name: health check
kind: http
request:
path: /health
method: GET
timeout: 5s
expect:
status_code: 200
body_asserts:
status: "ok"
header_asserts:
Content-Type:
present: trueMocks HTTP endpoints without running actual services.
- name: mock-service
kind: httpmock
app_port: 8080
routes:
- path: /api/users
method: GET
response:
status: 200
body:
users: []
headers:
Content-Type: application/jsonRuns your actual application in a container.
- name: my-app
kind: http
dockerfile: Dockerfile # Build from Dockerfile
# OR
image: myapp:latest # Use existing image
app_port: 8080
healthcheck: /health # Healthcheck endpoint
startup_timeout: 2m
env_file: .env
env:
- DATABASE_URL={{ mongodb.dsn }}
cmd: # Optional: Override container command
- ./server
- --port=8080Runs MongoDB with optional migrations.
- name: database
kind: mongo
image: mongo:6.0
app_port: 27017
database: testdb
user: testuser
password: testpass
migrations: db.js # JavaScript migration file
startup_timeout: 30sRuns MinIO object storage for S3-compatible testing.
- name: storage
kind: minio
image: minio/minio:latest
access_key: testuser
secret_key: testpass123
app_port: 9000
console_port: 9001
startup_timeout: 30s
buckets:
- uploads
- processedRuns PostgreSQL database with migrations.
- name: postgres
kind: postgres
image: postgres:14
app_port: 5432
database: testdb
user: testuser
password: testpass
migrations: migrations/ # SQL migration files
startup_timeout: 30s- name: create user
kind: http
request:
method: POST
path: /api/users
timeout: 5s
headers:
Content-Type: application/json
Authorization: Bearer {{ api_token }}
body:
name: John Doe
email: john@example.com
query_params:
include: profile
expect:
status_code: 201
body_asserts:
id:
present: true
name: John Doe
email:
matches: "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$"
header_asserts:
Location:
present: true
contains: /api/users/- name: verify user count
kind: postgres
query: "SELECT COUNT(*) as count FROM users WHERE status = 'active'"
expect:
row_count: 1
column_values:
count: 5
- name: check user data
kind: postgres
query: "SELECT id, name, email FROM users WHERE id = 1"
expect:
rows:
- id: 1
name: "Alice"
email: "alice@example.com"
- name: no orphaned orders
kind: postgres
query: "SELECT * FROM orders WHERE user_id NOT IN (SELECT id FROM users)"
expect:
no_rows: trueSee POSTGRES_TESTS.md for more examples.
- name: check active users
kind: mongo
collection: users
filter:
status: active
age:
$gte: 18
expect:
document_count: 5
- name: aggregate by role
kind: mongo
collection: users
pipeline:
- $group:
_id: "$role"
count: { $sum: 1 }
- $sort:
count: -1
expect:
min_document_count: 2
- name: verify user details
kind: mongo
collection: users
filter:
_id: 1
expect:
documents:
- _id: 1
name: "Alice"
email: "alice@example.com"See MONGO_QUICK_REFERENCE.md for more examples.
- name: verify file uploaded
kind: minio
verify_state:
files_exist:
- uploads/user123/profile.jpg
- uploads/user123/resume.pdf
bucket_counts:
uploads: 2
processed: 0
required:
buckets:
uploads:
- path: user123/profile.jpg
min_size: 10B
max_size: 10MB
content_type: image/jpeg
max_age: 5mbody_asserts:
# String shorthand for equals
data.user.name: "John Doe"
# Explicit assertions with object
data.user.email:
contains: "John" # Substring match
not_contains: "Admin" # Substring not present
matches: "^John.*" # Regex match
not_matches: "^Admin.*" # Regex not match
present: true # Field exists
type: string # Type check (string, int, float, bool, array, object)
# Comparison operators with symbols
data.user.age:
">": 10 # greater_than (numeric comparison)
"<": 100 # less_than (numeric comparison)
# Length checking
data.user.tags:
length: 5 # Array/string length (or 'size')header_asserts:
# String shorthand for equals
Content-Type: application/json
# Explicit assertions with object
X-Custom-Header:
contains: json
not_contains: xml
matches: "^application/.*"
not_matches: "^text/.*"
present: trueFixtures allow you to define reusable values that can be interpolated in your test configuration.
fixtures:
- api_base: http://api.example.com
- auth_token: Bearer abc123
- test_data: { file: ./testdata/user.json } # Load from file
# Use fixtures with {{ fixture_name }}
tests:
- name: test with fixtures
kind: http
request:
path: "{{ api_base }}/users"
headers:
Authorization: "{{ auth_token }}"
body: "{{ test_data }}"Service Variable Interpolation:
Access service properties using {{ service_name.property }}:
env:
- DATABASE_URL={{ mongodb.dsn }}
- MONGO_HOST={{ mongodb.host }}
- MONGO_PORT={{ mongodb.port }}
- REDIS_URL={{ redis.endpoint }}
- MINIO_ENDPOINT={{ storage.local_endpoint }}# Create test
ene scaffold-test simple-mock --tmpl=httpmock
# Edit tests/simple-mock/suite.yml
cat > tests/simple-mock/suite.yml <<EOF
kind: e2e_test:v1
name: simple-mock
units:
- name: api
kind: httpmock
app_port: 8080
routes:
- path: /ping
method: GET
response:
status: 200
body:
message: pong
target: api
tests:
- name: ping test
kind: http
request:
path: /ping
method: GET
expect:
status_code: 200
body_asserts:
message: pong
EOF
# Run test
ene --suite=simple-mock --verbose# Create test with MongoDB
ene scaffold-test db-test --tmpl=mongo,http
# Create migration file
cat > tests/db-test/db.js <<EOF
db.users.insertMany([
{ name: "Alice", email: "alice@example.com" },
{ name: "Bob", email: "bob@example.com" }
]);
EOF
# Run test
ene --suite=db-test --verbose# Run all tests in parallel with reports
ene --parallel --html=report.html --json=report.json --verbose
# Run specific suites
ene --suite=api-tests,integration-tests --verbose
# Run with debug information
ene --debug --verbose --suite=failing-test# Validate configuration in CI
ene dry-run --verbose
if [ $? -ne 0 ]; then
echo "Test configuration is invalid"
exit 1
fi
# Run tests with reports
ene --parallel --json=test-results.json --html=test-report.html
# Check exit code
if [ $? -ne 0 ]; then
echo "Tests failed"
exit 1
fiGitHub Actions Example:
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Build ENE
run: go build -o ene main.go
- name: Validate Configuration
run: ./ene dry-run --verbose
- name: Run Tests
run: ./ene --parallel --json=results.json --html=report.html
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-reports
path: |
results.json
report.html-
Use Parallel Execution: For faster test runs with independent test suites
ene --parallel
-
Cache Cleanup: Prevent Docker image bloat
ene --cleanup-cache
-
Targeted Testing: Run only specific suites during development
ene --suite=unit-tests
-
Optimize Timeouts: Adjust startup timeouts based on your services
startup_timeout: 30s # Reduce for faster services
-
Enable Verbose Output:
ene --verbose --suite=failing-test
-
Enable Debug Mode:
ene --debug --verbose --suite=failing-test
-
Run Without Parallel:
ene --suite=failing-test # Easier to read logs -
Check Docker Containers:
docker ps -a # View running/stopped containers docker logs <container-id> # View container logs
Enable shell completion for better UX:
# Bash
source <(ene completion bash)
# Zsh
source <(ene completion zsh)
# Fish
ene completion fish | sourceThis enables auto-completion for:
- Commands
- Flags
- Test suite names (for
--suiteflag)
▶ TEST RUN STARTED
══════════════════════════════════════════════════════════════════════════
[1/3] api-tests
⋯ Setting up network...
✓ health check 15ms
✓ create user 42ms
✓ get user 28ms
(Setup: 1.2s | Tests: 85ms | Overhead: 0ms)
[2/3] integration-tests
⋯ Setting up network...
✓ database integration 156ms
(Setup: 2.5s | Tests: 156ms | Overhead: 0ms)
════════════════════════════════════════════════════════════════════════════
SUMMARY 4.2s
Setup: 3.7s | Tests: 241ms | Overhead: 260ms
0 failed | 4 passed | 1 skipped
When using --html=report.html, generates a comprehensive HTML report with:
- Test summary and statistics
- Individual test results with timing
- Failed test details with error messages
- Pass/fail charts and visualizations
When using --json=report.json, generates machine-readable JSON:
{
"metadata": {
"startTime": "2024-01-15T10:30:00Z",
"endTime": "2024-01-15T10:30:05Z",
"durationMs": 5000,
"totalTests": 10,
"totalPassed": 9,
"totalFailed": 1,
"totalSkipped": 0
},
"suites": [
{
"name": "api-tests",
"tests": [...]
}
]
}1. Docker Connection Error
Error: Cannot connect to Docker daemon
Solution: Ensure Docker is running:
docker ps2. Port Already in Use
Error: Port 8080 is already in use
Solution: Change the port in your suite.yml or stop the conflicting service
3. Container Startup Timeout
Error: Container failed to start within timeout
Solution: Increase startup_timeout in unit configuration:
startup_timeout: 2m4. Test File Not Found
Error: failed to open test suite file
Solution: Ensure suite.yml exists in the test directory:
ls tests/my-test/suite.yml5. Invalid Configuration
Configuration validation failed
Solution: Run dry-run to see specific errors:
ene dry-run tests/my-test/suite.yml --verboseENE respects the following environment variables:
| Variable | Description | Default |
|---|---|---|
DOCKER_API_VERSION |
Docker API version | 1.45 |
DOCKER_HOST |
Docker daemon host | unix:///var/run/docker.sock |
To contribute to ENE or report issues, please visit the project repository.
[Insert License Information]
For issues, questions, or feature requests, please:
- Check this documentation
- Run tests with
--debug --verbosefor detailed output - Open an issue on the project repository