Skip to content

exapsy/ene

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

61 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ENE Logo

ENE - End-to-End Testing Framework

Go Version Docker

ENE is a powerful Docker-based end-to-end testing framework that spins up complete test environments with databases, services, and mocked APIs to validate your applications through comprehensive integration tests.

Demo

🌟 Key Features

  • 🐳 Docker-Native: Automatically manages containers for services, databases, and mocks
  • πŸ”§ Multiple Service Types: HTTP servers, MongoDB, PostgreSQL, MinIO, HTTP mocks
  • πŸ—„οΈ Database Testing: First-class support for PostgreSQL and MongoDB query testing
  • πŸ“ Simple YAML Configuration: Declarative test definitions with fixtures and assertions
  • πŸ”„ Variable Interpolation: Reuse values across tests with fixtures and service variables
  • πŸ“Š Rich Assertions: JSON path queries, SQL/NoSQL result validation, header checks, MinIO state verification
  • 🎯 Test Isolation: Each suite runs in its own Docker network
  • πŸ“ˆ Detailed Reports: HTML and JSON output formats
  • ⚑ Parallel Execution: Run multiple test suites concurrently
  • πŸ› οΈ Easy Scaffolding: Generate new test suites with templates

πŸ“‹ Prerequisites

  • Docker (with daemon running)
  • Docker Compose (for orchestration)
  • Go 1.25+ (to build from source)

macOS/Colima Users

If using Colima on macOS, set these environment variables:

export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock
export DOCKER_HOST="unix://${HOME}/.colima/docker.sock"

πŸš€ Quick Start

Installation

# Clone and build
git clone https://github.com/exapsy/ene
cd ene
go build -o ene .

# Optional: Install globally
sudo mv ene /usr/local/bin/

# Verify installation
ene version

Your First Test

# Create a new test suite
ene scaffold-test my-first-test --tmpl=httpmock

# This creates: ./tests/my-first-test/suite.yml

Edit tests/my-first-test/suite.yml:

kind: e2e_test:v1
name: my-first-test

fixtures:
  - api_key: test-key-123

units:
  - name: api
    kind: httpmock
    app_port: 8080
    routes:
      - path: /health
        method: GET
        response:
          status: 200
          body:
            status: ok

target: api

tests:
  - name: health check
    kind: http
    request:
      path: /health
      method: GET
      headers:
        Authorization: Bearer {{ api_key }}
    expect:
      status_code: 200
      body_asserts:
        status: ok

Run your test:

# Validate configuration
ene dry-run

# Run the test
ene

# Run with verbose output
ene --verbose

# Run with HTML report
ene --html=report.html

πŸ“ Project Structure

ENE uses flexible test suite discovery and supports multiple directory structures:

Recommended Structure

your-project/
└── tests/                    # Test suites directory
    β”œβ”€β”€ suite-name-1/
    β”‚   β”œβ”€β”€ suite.yml        # Test configuration (required)
    β”‚   β”œβ”€β”€ Dockerfile       # Optional service dockerfile
    β”‚   β”œβ”€β”€ .env             # Optional environment variables
    β”‚   β”œβ”€β”€ db.js            # Optional MongoDB migrations
    β”‚   └── migrations/      # Optional PostgreSQL migrations
    └── suite-name-2/
        └── suite.yml

Flexible Discovery

ENE intelligently discovers test suites based on the path you provide:

Run all tests from project root:

cd your-project
ene                    # Discovers tests/ automatically (if it exists)

Run a specific suite:

ene tests/suite-name-1          # By directory
ene tests/suite-name-1/suite.yml # By file

Run from any directory:

cd tests/suite-name-1
ene                    # Runs the suite in current directory

cd /anywhere
ene /path/to/tests/suite-name-1

Nested structures work too:

tests/
  β”œβ”€β”€ integration/
  β”‚   β”œβ”€β”€ auth/suite.yml
  β”‚   └── payments/suite.yml
  └── unit/
      └── api/suite.yml

# Run all integration tests
ene tests/integration

# Run just auth tests
ene tests/integration/auth

πŸ“– Learn more: See Test Discovery Documentation for detailed discovery rules and examples.

🎯 Common Commands

# Run all tests
ene

# Run with verbose output
ene --verbose

# Run specific suite by path
ene tests/my-test
ene tests/integration/auth

# Run specific suite(s) by filtering
ene --suite=my-test
ene --suite=test1,test2

# Run tests matching pattern
ene --suite=user_,_api

# Run in parallel
ene --parallel

# Validate without running
ene dry-run
ene dry-run tests/my-test

# Generate reports
ene --html=report.html --json=report.json

# List all test suites
ene list-suites
ene list-suites tests/integration

# Create new test suite
ene scaffold-test my-new-test
ene scaffold-test api-test --tmpl=postgres,http

# Enable debug mode
ene --debug --verbose

# Cleanup old Docker images
ene --cleanup-cache

# Cleanup orphaned Docker resources
ene cleanup --dry-run            # Preview what would be removed
ene cleanup --force              # Remove orphaned resources
ene cleanup --older-than=1h      # Remove resources older than 1 hour

πŸ“ Configuration Reference

Suite Configuration (suite.yml)

kind: e2e_test:v1              # Required: Version identifier
name: my-test-suite            # Required: Suite name

# Optional: Reusable values
fixtures:
  - user_id: 12345            # Simple value
  - enabled: true             # Boolean
  - api_key: test-key-xyz     # String
  - test_data:                # File-based fixture
      file: ./data/user.json

# Required: Service containers
units:
  - name: postgres
    kind: postgres
    image: postgres:14
    app_port: 5432
    database: testdb
    user: testuser
    password: testpass
    migrations: ./migrations
  
  - name: app
    kind: http
    dockerfile: ./Dockerfile
    app_port: 8080
    healthcheck: /health
    build_timeout: 45s
    startup_timeout: 30s
    env:
      - DATABASE_URL={{ postgres.dsn }}
      - API_KEY={{ api_key }}

# Required: Default target service
target: app

# Required: Test cases
tests:
  - name: create user
    kind: http
    request:
      method: POST
      path: /api/users
      headers:
        Content-Type: application/json
      body:
        name: John Doe
        email: john@example.com
    expect:
      status_code: 201
      body_asserts:
        id:
          present: true
        name: John Doe

Supported Unit Types

HTTP Service

- name: app
  kind: http
  dockerfile: Dockerfile    # OR image: myapp:latest
  app_port: 8080
  healthcheck: /health      # Optional health endpoint
  build_timeout: 45s        # Time allowed for building Docker image (default: 45s)
  startup_timeout: 30s      # Time allowed for service to become healthy (default: 30s)
  env:
    - KEY=value
  cmd:                      # Optional command override
    - ./app
    - --port=8080

Timeouts Explained:

  • build_timeout: Maximum time for Docker image build (downloading dependencies, compilation)
  • startup_timeout: Maximum time for container startup and health check after build completes

HTTP Mock

- name: mock-api
  kind: httpmock
  app_port: 8080
  routes:
    - path: /api/users
      method: GET
      response:
        status: 200
        delay: 100ms        # Optional delay
        body:
          users: []
        headers:
          Content-Type: application/json

PostgreSQL

- name: postgres
  kind: postgres
  image: postgres:14
  app_port: 5432
  database: testdb
  user: testuser
  password: testpass
  migrations: ./migrations  # Directory with .sql files
  startup_timeout: 30s

MongoDB

- name: mongodb
  kind: mongo
  image: mongo:6.0
  app_port: 27017
  database: testdb
  user: testuser
  password: testpass
  migrations: db.js         # JavaScript migration file
  startup_timeout: 30s

MinIO

- name: storage
  kind: minio
  image: minio/minio:latest
  access_key: testkey
  secret_key: testsecret
  app_port: 9000
  console_port: 9001
  buckets:
    - uploads
    - processed

Fixtures

Simple Values (Primitives):

fixtures:
  - api_key: test-key-123
  - user_id: 5432
  - enabled: true
  - description: |
      Multi-line
      text value

File-Based Fixtures:

fixtures:
  - test_payload:
      file: ./testdata/payload.json
  - large_data:
      file: ./testdata/users.json

Usage in Tests:

tests:
  - name: test
    kind: http
    request:
      path: /users/{{ user_id }}
      headers:
        Authorization: Bearer {{ api_key }}
      body: "{{ test_payload }}"

Service Variables

Access service connection details:

# PostgreSQL
{{ postgres.dsn }}         # postgresql://user:pass@host:port/db
{{ postgres.host }}
{{ postgres.port }}
{{ postgres.database }}

# MongoDB
{{ mongodb.dsn }}          # mongodb://user:pass@host:port/db
{{ mongodb.host }}
{{ mongodb.port }}
{{ mongodb.database }}

# MinIO
{{ storage.endpoint }}     # External endpoint
{{ storage.local_endpoint }} # Internal Docker endpoint
{{ storage.access_key }}
{{ storage.secret_key }}

# HTTP Service
{{ app.host }}
{{ app.port }}
{{ app.endpoint }}         # http://app:8080

Supported Test Types

ENE supports multiple test types for different testing scenarios:

HTTP Tests (kind: http)

Test HTTP APIs with full request/response validation:

tests:
  - name: create user
    kind: http
    request:
      method: POST
      path: /api/users
      headers:
        Content-Type: application/json
      body:
        name: John Doe
        email: john@example.com
    expect:
      status_code: 201
      body_asserts:
        id:
          present: true
        name: John Doe

PostgreSQL Tests (kind: postgres)

Execute SQL queries and validate results:

tests:
  - 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"

πŸ“– See POSTGRES_TESTS.md for complete PostgreSQL testing guide

MongoDB Tests (kind: mongo)

Query MongoDB collections with find operations or aggregation pipelines:

tests:
  - name: check active users
    kind: mongo
    collection: users
    filter:
      status: active
    expect:
      document_count: 5
  
  - name: aggregate by role
    kind: mongo
    collection: users
    pipeline:
      - $group:
          _id: "$role"
          count: { $sum: 1 }
    expect:
      min_document_count: 2

πŸ“– See MONGO_QUICK_REFERENCE.md for complete MongoDB testing guide

MinIO Tests (kind: minio)

Verify object storage state:

tests:
  - name: verify upload
    kind: minio
    verify_state:
      files_exist:
        - uploads/file1.txt
      bucket_counts:
        uploads: 2
      required:
        buckets:
          uploads:
            - path: file1.txt
              min_size: 10B
              max_size: 10MB

Assertions

Body Assertions (JSON Path):

body_asserts:
  # Simple equality (shorthand)
  status: ok
  
  # Detailed assertions
  user.id:
    present: true           # Key exists
    type: string           # Type check
  
  user.age:
    ">": 18                # Numeric comparison
    "<": 100
  
  items:
    length: 5              # Array length
    type: array
  
  # Array containment
  products:
    contains_where:
      name: iPhone
      price:
        ">": 900
  
  # All items must match
  users:
    all_match:
      active: true
  
  # No items should match
  errors:
    none_match:
      critical: true

Header Assertions:

header_asserts:
  Content-Type: application/json  # Simple equality
  
  X-Request-ID:
    present: true
    matches: "^[0-9a-f-]{36}$"
  
  Cache-Control:
    contains: no-cache

MinIO State Verification:

- name: verify upload
  kind: minio
  verify_state:
    files_exist:
      - uploads/file1.txt
    
    bucket_counts:
      uploads: 2
    
    required:
      buckets:
        uploads:
          - path: file1.txt
            min_size: 10B
            max_size: 10MB
            content_type: text/plain
            max_age: 5m
    
    forbidden:
      buckets:
        uploads:
          - "*.tmp"

🧹 Resource Management & Cleanup

ENE automatically manages Docker resources (containers, networks) during test execution. However, if tests are interrupted or fail, resources may be left behind.

Automatic Cleanup

ENE uses a CleanupRegistry that ensures proper cleanup order:

  1. Containers are removed first
  2. Networks are removed after containers detach
  3. Resources are cleaned even if individual cleanups fail

This prevents common errors like "network has active endpoints."

Manual Cleanup Command

Use the ene cleanup command to remove orphaned resources:

# Interactive cleanup (shows what will be removed)
ene cleanup

# Preview without removing (dry-run)
ene cleanup --dry-run --verbose

# Force cleanup without confirmation
ene cleanup --force

# Clean specific resource types
ene cleanup networks             # Networks only
ene cleanup containers           # Containers only

# Age-based filtering
ene cleanup --older-than=1h      # Resources older than 1 hour
ene cleanup --older-than=24h     # Resources older than 1 day

# Include all resources (not just orphaned)
ene cleanup --all --force

# Verbose output for debugging
ene cleanup --verbose

Best Practices

For CI/CD:

# GitLab CI
after_script:
  - ene cleanup --older-than=30m --force
  
# GitHub Actions
- name: Cleanup
  if: always()
  run: ene cleanup --older-than=30m --force

For Local Development:

# Check for orphaned resources
ene cleanup --dry-run --verbose

# Clean up after testing
ene cleanup --force

Periodic Cleanup (Cron):

# Add to crontab for nightly cleanup
0 2 * * * /usr/local/bin/ene cleanup --older-than=24h --force

Troubleshooting Resource Leaks

If you see orphaned Docker resources:

# 1. Discover what's orphaned
ene cleanup --dry-run --verbose

# 2. Check Docker resources manually
docker network ls | grep testcontainers
docker ps -a | grep testcontainers

# 3. Clean them up
ene cleanup --force

# 4. For stubborn resources, inspect and remove manually
docker network inspect <network-id>
docker rm -f <container-id>
docker network rm <network-id>

For more details, see:

πŸ” Debugging

Enable Verbose Logging

ene --verbose --debug

Check Docker Containers

docker ps -a
docker logs <container-id>

Validate Configuration

ene dry-run --verbose

Common Issues

Port Already in Use:

  • Change app_port in your suite.yml
  • Check for conflicting services: lsof -i :8080

Startup Timeout:

  • Increase startup_timeout for the unit
  • Check service logs for errors

Test Failures:

  • Use --verbose to see detailed request/response
  • Check assertion paths match your JSON structure
  • Verify fixtures are interpolated correctly

πŸ“š Documentation

🀝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for new features
  4. Submit a pull request

πŸ“„ License

MIT

πŸ†˜ Support

🎯 Roadmap

  • WebSocket testing support
  • gRPC testing support
  • Redis unit type
  • Kafka unit type
  • Test retry strategies
  • Performance benchmarking
  • Visual test reports

Made with ❀️ for better end-to-end testing

About

End to End testing framework

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages