Skip to content

Using Containers in Tests

This guide shows how to integrate Solti-Containers services into your testing workflows.

Testing Philosophy

The containers are designed for ephemeral testing environments:

  1. Fast Setup: Containers start in seconds
  2. Isolation: Each test run can use fresh containers
  3. Cleanup: Easy removal after tests complete
  4. Reproducibility: Same environment every time

Integration Patterns

Pattern 1: Manual Lifecycle

For interactive testing and development:

# Start services before testing
./manage-svc.sh redis deploy
./manage-svc.sh mattermost deploy

# Run your tests
pytest tests/integration/

# Clean up after
./manage-svc.sh redis remove
./manage-svc.sh mattermost remove

Pattern 2: Test Wrapper Script

Create a test wrapper that manages container lifecycle:

#!/bin/bash
# test-with-containers.sh

# Deploy required services
echo "Deploying test services..."
./manage-svc.sh redis deploy
./manage-svc.sh elasticsearch deploy

# Wait for services to be ready
./svc-exec.sh redis verify
./svc-exec.sh elasticsearch verify

# Run tests
echo "Running tests..."
pytest "$@"
TEST_EXIT=$?

# Cleanup
echo "Cleaning up..."
./manage-svc.sh redis remove
./manage-svc.sh elasticsearch remove

exit $TEST_EXIT

Usage:

./test-with-containers.sh tests/integration/

Pattern 3: Makefile Integration

.PHONY: test-integration test-setup test-cleanup

test-setup:
    ./manage-svc.sh redis deploy
    ./manage-svc.sh mattermost deploy
    ./svc-exec.sh redis verify
    ./svc-exec.sh mattermost verify

test-integration: test-setup
    pytest tests/integration/ || (make test-cleanup && exit 1)
    make test-cleanup

test-cleanup:
    ./manage-svc.sh redis remove
    ./manage-svc.sh mattermost remove

Usage:

make test-integration

Pattern 4: Python Test Fixtures

Using pytest fixtures for container management:

# conftest.py
import pytest
import subprocess
import time

@pytest.fixture(scope="session")
def redis_container():
    """Deploy Redis container for testing session."""
    # Deploy
    subprocess.run(["./manage-svc.sh", "redis", "deploy"], check=True)
    time.sleep(2)  # Wait for startup

    # Verify
    subprocess.run(["./svc-exec.sh", "redis", "verify"], check=True)

    yield "localhost:6379"

    # Cleanup
    subprocess.run(["./manage-svc.sh", "redis", "remove"], check=True)

@pytest.fixture(scope="session")
def mattermost_container():
    """Deploy Mattermost container for testing session."""
    subprocess.run(["./manage-svc.sh", "mattermost", "deploy"], check=True)
    time.sleep(30)  # Mattermost takes longer to start

    subprocess.run(["./svc-exec.sh", "mattermost", "verify"], check=True)

    yield "http://localhost:8065"

    subprocess.run(["./manage-svc.sh", "mattermost", "remove"], check=True)

# Test usage
def test_redis_caching(redis_container):
    import redis
    r = redis.Redis.from_url(f"redis://{redis_container}")
    r.set("test", "value")
    assert r.get("test") == b"value"

def test_mattermost_webhook(mattermost_container):
    import requests
    # Setup webhook and test
    pass

CI/CD Integration

GitHub Actions

name: Integration Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Install Podman
        run: |
          sudo apt-get update
          sudo apt-get install -y podman

      - name: Install Ansible
        run: pip install ansible

      - name: Deploy test services
        run: |
          ./manage-svc.sh redis deploy
          ./manage-svc.sh mattermost deploy

      - name: Verify services
        run: |
          ./svc-exec.sh redis verify
          ./svc-exec.sh mattermost verify

      - name: Run tests
        run: pytest tests/integration/

      - name: Cleanup
        if: always()
        run: |
          ./manage-svc.sh redis remove
          ./manage-svc.sh mattermost remove

GitLab CI

integration-test:
  image: debian:bookworm

  before_script:
    - apt-get update
    - apt-get install -y podman ansible python3-pip
    - pip3 install pytest

  script:
    - ./manage-svc.sh redis deploy
    - ./manage-svc.sh elasticsearch deploy
    - ./svc-exec.sh redis verify
    - ./svc-exec.sh elasticsearch verify
    - pytest tests/integration/

  after_script:
    - ./manage-svc.sh redis remove || true
    - ./manage-svc.sh elasticsearch remove || true

Molecule Testing

For testing Ansible roles with containers:

# molecule/default/molecule.yml
---
dependency:
  name: galaxy

driver:
  name: podman

platforms:
  - name: test-instance
    image: debian:bookworm
    pre_build_image: true

provisioner:
  name: ansible
  inventory:
    group_vars:
      all:
        redis_state: present
        mattermost_state: present

verifier:
  name: ansible
# molecule/default/verify.yml
---
- name: Verify
  hosts: all
  tasks:
    - name: Verify Redis is accessible
      command: redis-cli -h localhost ping
      register: redis_ping
      failed_when: redis_ping.stdout != "PONG"

    - name: Verify Mattermost is accessible
      uri:
        url: http://localhost:8065
        status_code: 200

Service Dependencies

When tests require multiple services:

# conftest.py
@pytest.fixture(scope="session")
def test_environment():
    """Deploy all required services."""
    services = ["redis", "mattermost", "elasticsearch"]

    # Deploy all
    for service in services:
        subprocess.run(["./manage-svc.sh", service, "deploy"], check=True)

    # Wait for all to be ready
    time.sleep(10)

    # Verify all
    for service in services:
        subprocess.run(["./svc-exec.sh", service, "verify"], check=True)

    yield {
        "redis": "localhost:6379",
        "mattermost": "http://localhost:8065",
        "elasticsearch": "http://localhost:9200"
    }

    # Cleanup all
    for service in services:
        subprocess.run(["./manage-svc.sh", service, "remove"], check=True)

Best Practices

1. Use Session Fixtures

Deploy containers once per test session, not per test:

@pytest.fixture(scope="session")  # Not scope="function"
def redis_container():
    # ...

2. Verify Before Testing

Always verify services are ready before running tests:

./svc-exec.sh redis verify || exit 1

3. Handle Cleanup

Use try/finally or if: always() in CI to ensure cleanup:

- name: Cleanup
  if: always()
  run: |
    ./manage-svc.sh redis remove

4. Parallel Testing

For parallel test execution, use unique port assignments:

# test-env-1
redis_port: 6379
mattermost_port: 8065

# test-env-2
redis_port: 6380
mattermost_port: 8066

5. Data Isolation

Always use remove between test runs to ensure clean state:

./manage-svc.sh redis remove  # Keeps data
./manage-svc.sh redis remove -e redis_delete_data=true  # Deletes data

Common Patterns by Service

Redis

  • Cache testing
  • Session storage tests
  • Queue processing tests
  • Rate limiting tests

Mattermost

  • Webhook delivery tests
  • Notification integration tests
  • Bot interaction tests

Elasticsearch

  • Search functionality tests
  • Log ingestion tests
  • Analytics pipeline tests

HashiVault

  • Secret retrieval tests
  • Dynamic credential tests
  • Certificate generation tests

MinIO

  • S3 API compatibility tests
  • Backup workflow tests
  • Object storage tests

Troubleshooting

Services Not Starting

# Check status
systemctl --user status <service>-pod

# View logs
podman logs <service>-svc

# Retry deployment
./manage-svc.sh <service> remove
./manage-svc.sh <service> deploy

Port Conflicts

# Check what's using a port
sudo lsof -i :6379

# Use different port
./manage-svc.sh redis deploy -e redis_port=6380

Slow Startup

Some services (Elasticsearch, Mattermost) take longer to start:

# Increase wait time
time.sleep(30)  # For Mattermost
time.sleep(60)  # For Elasticsearch

Or check health endpoints:

import time
import requests

def wait_for_service(url, timeout=60):
    start = time.time()
    while time.time() - start < timeout:
        try:
            response = requests.get(url)
            if response.status_code == 200:
                return True
        except requests.exceptions.ConnectionError:
            pass
        time.sleep(2)
    return False

wait_for_service("http://localhost:8065")  # Mattermost