Skip to content

Testing Guide

envdrift has a comprehensive test suite covering unit tests, integration tests, and end-to-end workflows.

Running Tests

Quick Commands

# Run all tests (unit + integration)
make test

# Run only unit tests
uv run pytest -m "not integration"

# Run only integration tests
uv run pytest -m integration

# Run integration tests with Docker containers
make test-integration

Integration Tests with Docker

Some integration tests require Docker containers (LocalStack, HashiCorp Vault, Azure emulator):

# Start test containers
docker-compose -f tests/docker-compose.test.yml up -d

# Run integration tests
uv run pytest tests/integration/ -v

# Stop containers when done
docker-compose -f tests/docker-compose.test.yml down

Test Categories

Unit Tests (tests/unit/)

Fast, isolated tests that mock external dependencies.

Module Coverage
CLI commands Command parsing, output formatting
Encryption dotenvx/SOPS wrapper logic
Config parsing pyproject.toml/envdrift.toml loading
Vault clients AWS, HashiCorp, Azure, GCP client logic
Git utilities Hook installation, status detection

Integration Tests (tests/integration/)

Tests that exercise real components, potentially with external services.

Infrastructure Tests

File Purpose
test_infrastructure.py Verifies Docker containers are accessible
conftest.py Session-scoped fixtures for containers

Encryption Tests

File Tests
test_encryption_tools.py dotenvx/SOPS encrypt/decrypt roundtrips
test_encryption_edge_cases.py Unicode, multiline, empty files, mixed state
test_ephemeral_keys.py Ephemeral mode (no local key storage)

Vault Provider Tests

File Provider Tests
test_aws_integration.py AWS Secrets Manager (LocalStack) Sync, push, list, error handling
test_hashicorp_integration.py HashiCorp Vault KV v2 operations, token auth
test_azure_integration.py Azure Key Vault (Lowkey Vault) Secret operations

Workflow Tests

File Tests
test_e2e_workflows.py Full pull→decrypt, lock→push, monorepo, CI mode
test_hook_setup.py Git hook installation
test_git_hooks_advanced.py Hooks block/allow commits, pre-push checks

Error Handling & Concurrency

File Tests
test_error_handling.py Network timeouts, partial failures, corrupt files
test_concurrency.py Thread safety, race conditions, parallel operations

Test Markers

Tests are tagged with pytest markers for selective execution:

# Run only AWS-related tests
uv run pytest -m aws

# Run only HashiCorp Vault tests
uv run pytest -m vault

# Run only Azure tests
uv run pytest -m azure

# Skip slow tests
uv run pytest -m "not slow"

Available markers:

Marker Description
integration Requires external tools (dotenvx, sops)
aws Requires LocalStack container
vault Requires HashiCorp Vault container
azure Requires Lowkey Vault container
slow Tests that take >10 seconds

CI Pipelines

Main CI (ci.yml)

Runs on every PR:

  • Python 3.12
  • Unit tests + integration tests (containers skipped if unavailable)
  • Linting and coverage upload

Integration Tests (integration-tests.yml)

Runs on every PR with full Docker support:

  • Python 3.11, 3.12, 3.13, 3.14 matrix
  • Service containers: LocalStack, HashiCorp Vault, Lowkey Vault
  • Full integration test suite
  • Coverage reporting to Codecov

Writing New Tests

Unit Test Guidelines

# tests/unit/test_example.py
import pytest
from unittest.mock import MagicMock, patch

def test_something_simple():
    """Test description."""
    result = my_function("input")
    assert result == "expected"

@patch("envdrift.vault.aws.boto3")
def test_with_mock(mock_boto3):
    """Mock external dependencies."""
    mock_boto3.client.return_value = MagicMock()
    # ... test logic

Integration Test Guidelines

# tests/integration/test_example.py
import pytest
from pathlib import Path

pytestmark = [pytest.mark.integration]

class TestMyFeature:
    """Test class description."""

    def test_with_fixtures(
        self,
        work_dir: Path,
        integration_pythonpath: str,
    ) -> None:
        """Test using common fixtures."""
        # work_dir is a temporary directory
        # integration_pythonpath sets up imports
        pass

    @pytest.mark.aws
    def test_with_aws(
        self,
        localstack_endpoint: str,
        aws_test_env: dict[str, str],
    ) -> None:
        """Test requiring AWS (LocalStack)."""
        # Skips automatically if LocalStack not available
        pass

Available Fixtures

Fixture Scope Description
work_dir function Clean temporary directory
git_repo function Initialized git repository
integration_pythonpath session PYTHONPATH for imports
localstack_endpoint session LocalStack URL (skips if unavailable)
aws_test_env session Environment variables for AWS
aws_secrets_client session boto3 Secrets Manager client
vault_endpoint session HashiCorp Vault URL
vault_test_env session Environment variables for Vault
vault_client session hvac client
lowkey_vault_endpoint session Azure emulator URL
azure_test_env session Environment variables for Azure

Test Infrastructure

Docker Compose Services

The tests/docker-compose.test.yml provides:

Service Port Purpose
LocalStack 4566 AWS Secrets Manager emulator
Vault 8200 HashiCorp Vault (dev mode)
Lowkey Vault 8443 Azure Key Vault emulator

Health Checks

All services include health checks. The CI pipeline waits for services to be ready before running tests.