Matrix Testing
Learn how to test multiple services in parallel using GitHub Actions matrix strategy with isolated Docker environments.
Overview
Matrix testing allows you to run HTTPTests for multiple services simultaneously, each in its own isolated environment, providing faster feedback and better parallelization.
The Problem
When you have multiple services to test:
your-repo/
├── service-a/.httptests/
├── service-b/.httptests/
├── service-c/.httptests/
├── service-d/.httptests/
└── service-e/.httptests/Sequential approach:
# ❌ Slow - runs one after another
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./service-a
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./service-b
# ... and so onProblems:
- ❌ Slow - tests run sequentially
- ❌ Long feedback cycles
- ❌ One failure blocks others
- ❌ Poor resource utilization
The Solution: Matrix Strategy
Run all services in parallel with GitHub Actions matrix strategy:
name: HTTPTests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
service:
- service-a
- service-b
- service-c
- service-d
- service-e
steps:
- uses: actions/checkout@v4
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./${{ matrix.service }}Benefits:
- ✅ Tests run in parallel
- ✅ Faster overall execution
- ✅ Independent failure isolation
- ✅ Better GitHub Actions UI
- ✅ Isolated Docker environments
Basic Matrix Configuration
Simple Service List
name: HTTPTests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
test-suite:
- ./api-gateway
- ./user-service
- ./order-service
- ./payment-service
steps:
- uses: actions/checkout@v4
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ${{ matrix.test-suite }}With Job Names
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
service:
- name: API Gateway
path: ./api-gateway
- name: User Service
path: ./user-service
- name: Order Service
path: ./order-service
name: Test ${{ matrix.service.name }}
steps:
- uses: actions/checkout@v4
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ${{ matrix.service.path }}Advanced Configurations
Fail-Fast vs Continue on Error
Fail-Fast (Default)
strategy:
matrix:
service: [service-a, service-b, service-c]
# fail-fast: true (default)Behavior: If one service fails, all others are cancelled.
Use when: You want quick feedback and failing fast saves time.
Continue on Error
strategy:
matrix:
service: [service-a, service-b, service-c]
fail-fast: falseBehavior: All services continue testing even if one fails.
Use when: You want to see all failures, not just the first one.
Timeout Configuration
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 10 # Per service timeout
strategy:
matrix:
service: [service-a, service-b, service-c]
steps:
- uses: actions/checkout@v4
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./${{ matrix.service }}Conditional Matrix
Run only changed services:
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
services: ${{ steps.filter.outputs.changes }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
service-a:
- 'services/service-a/**'
service-b:
- 'services/service-b/**'
service-c:
- 'services/service-c/**'
test:
needs: detect-changes
if: ${{ needs.detect-changes.outputs.services != '[]' }}
runs-on: ubuntu-latest
strategy:
matrix:
service: ${{ fromJSON(needs.detect-changes.outputs.services) }}
steps:
- uses: actions/checkout@v4
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./services/${{ matrix.service }}Complete Examples
Example 1: Microservices Monorepo
name: Microservices Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
service:
- api-gateway
- user-service
- order-service
- payment-service
- notification-service
name: Test ${{ matrix.service }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run HTTPTests
uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./services/${{ matrix.service }}
python-version: '3.11'Example 2: Different Service Categories
name: HTTPTests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- category: "API"
service: api-gateway
timeout: 10
- category: "API"
service: rest-api
timeout: 10
- category: "Proxy"
service: nginx-proxy
timeout: 5
- category: "Proxy"
service: load-balancer
timeout: 5
name: Test ${{ matrix.category }} - ${{ matrix.service }}
timeout-minutes: ${{ matrix.timeout }}
steps:
- uses: actions/checkout@v4
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./${{ matrix.service }}Example 3: Environment-Specific Testing
name: Multi-Environment Tests
on: [push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
service: [api-gateway, user-service]
environment: [staging, production]
name: Test ${{ matrix.service }} (${{ matrix.environment }})
steps:
- uses: actions/checkout@v4
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./${{ matrix.service }}-${{ matrix.environment }}Isolated Docker Environments
Each matrix job runs in its own runner with isolated Docker:
# Job 1: service-a
🚀 Starting environment...
docker compose -p httptests-service-a up -d
# Job 2: service-b (parallel)
🚀 Starting environment...
docker compose -p httptests-service-b up -d
# Job 3: service-c (parallel)
🚀 Starting environment...
docker compose -p httptests-service-c up -dKey features:
- ✅ Separate Docker networks per service
- ✅ No port conflicts
- ✅ Independent container lifecycle
- ✅ Automatic cleanup per job
GitHub Actions UI Benefits
Matrix testing provides clear UI visualization:
✅ test (api-gateway) - Passed in 2m 34s
✅ test (user-service) - Passed in 1m 52s
❌ test (order-service) - Failed in 3m 12s
✅ test (payment-service) - Passed in 2m 03s
✅ test (notification-service) - Passed in 1m 28sBenefits:
- See status of each service independently
- Quick identification of which service failed
- Individual logs per service
- Re-run only failed jobs
Best Practices
1. Use Descriptive Job Names
# Good
strategy:
matrix:
service:
- name: API Gateway
path: ./api-gateway
- name: User Service
path: ./user-service
name: Test ${{ matrix.service.name }}
# Bad
strategy:
matrix:
service: [./api-gateway, ./user-service]
name: Test ${{ matrix.service }} # Shows "./api-gateway"2. Set Appropriate Timeouts
jobs:
test:
timeout-minutes: 15 # Prevent hanging jobs
strategy:
matrix:
service: [...]3. Use fail-fast Appropriately
# Development - fail fast to save time
strategy:
matrix:
service: [...]
fail-fast: true
# CI/CD - see all failures
strategy:
matrix:
service: [...]
fail-fast: false4. Limit Concurrency for Resource Constraints
strategy:
max-parallel: 3 # Run max 3 jobs at once
matrix:
service: [s1, s2, s3, s4, s5, s6]5. Group Related Services
# Separate workflows for different layers
name: API Layer Tests
strategy:
matrix:
service: [api-gateway, rest-api, graphql-api]
---
name: Service Layer Tests
strategy:
matrix:
service: [user-service, order-service, payment-service]Comparison: Matrix vs Separate Jobs
Matrix Strategy (Recommended)
jobs:
test:
strategy:
matrix:
service: [a, b, c]Pros:
- ✅ Less code duplication
- ✅ Easier to add/remove services
- ✅ Better GitHub UI
- ✅ Consistent configuration
Cons:
- ⚠️ All jobs use same configuration
- ⚠️ Harder to customize per service
Separate Jobs
jobs:
test-a:
steps: [...]
test-b:
steps: [...]
test-c:
steps: [...]Pros:
- ✅ Full control per job
- ✅ Easy to customize each service
- ✅ Can have dependencies between jobs
Cons:
- ❌ More code duplication
- ❌ Harder to maintain
- ❌ More verbose
Troubleshooting
Tests Run Sequentially Instead of Parallel
Issue: Jobs appear to run one after another
Cause: GitHub Actions concurrent job limits
Solutions:
- Check your GitHub plan's concurrency limits
- Use
max-parallelto adjust - Check organization/repository settings
Resource Exhaustion
Issue: Jobs fail with "Docker daemon not responding"
Cause: Too many parallel Docker operations
Solutions:
strategy:
max-parallel: 5 # Limit concurrent jobs
matrix:
service: [...]Port Conflicts
Issue: "Port already in use" errors
Cause: Matrix jobs sharing ports (shouldn't happen)
Solutions:
- Each job gets isolated runner - this shouldn't occur
- If it does, check for host port mappings in docker-compose
- HTTPTests uses isolated project names automatically
Performance Tips
1. Optimize Docker Builds
# Use specific base image versions
FROM nginx:1.25-alpine
# Minimize layers
COPY nginx.conf /etc/nginx/nginx.conf
# No unnecessary packages2. Cache Dependencies
- name: Cache Python dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}3. Parallelize Appropriately
# If you have 20 services, don't run all at once
strategy:
max-parallel: 10 # Balance speed vs resourcesRelated Topics
- Installation - Basic workflow setup
- Microservices Example - Real-world usage
- GitHub Actions Documentation - Official matrix docs