Testing
Quick Start
uv sync --extra dev
uv run pytest tests/ -v -m "not slow"
Running Tests
# All unit tests
pytest tests/unit/ -v
# By marker
pytest tests/ -m unit
pytest tests/ -m integration
pytest tests/ -m "not container"
# Coverage
pytest tests/ -m "not slow" --cov=mcp_hangar --cov-report=html
Markers
| Marker | Description |
|---|---|
unit | Fast, isolated, no external dependencies |
integration | Multiple components working together |
container | Requires Docker/Podman containers |
slow | Long-running tests (>5 seconds) |
postgres | Requires PostgreSQL container |
redis | Requires Redis container |
langfuse | Requires Langfuse container |
prometheus | Requires Prometheus container |
property | Property-based tests using Hypothesis |
Testcontainers Integration
MCP Hangar uses Testcontainers for integration tests with real services.
Installation
pip install mcp-hangar[testcontainers]
# or
pip install "testcontainers[postgres]>=4.0.0" httpx
Running Container Tests
# Run all container tests (requires Docker/Podman)
pytest tests/integration/containers/ --run-containers -v
# Run specific container tests
pytest tests/integration/containers/test_postgres.py --run-containers -v
pytest tests/integration/containers/test_langfuse.py --run-containers -v
pytest tests/integration/containers/test_redis.py --run-containers -v
# Skip slow container tests
pytest tests/integration/containers/ --run-containers -m "not slow" -v
Available Container Fixtures
| Fixture | Description | Required Extra |
|---|---|---|
postgres_container | PostgreSQL 15 Alpine | testcontainers[postgres] |
redis_container | Redis 7 Alpine | testcontainers |
langfuse_container | Langfuse 2 with PostgreSQL | testcontainers[postgres] |
prometheus_container | Prometheus v2.47 | testcontainers |
math_provider_container | MCP Math MCP Server | Local image required |
sqlite_provider_container | MCP SQLite MCP Server | Local image required |
Example: Using PostgreSQL Container
import pytest
@pytest.mark.container
@pytest.mark.postgres
def test_database_operations(postgres_container):
"""Test with real PostgreSQL database."""
dsn = postgres_container["dsn"]
import asyncpg
conn = await asyncpg.connect(dsn)
result = await conn.fetchval("SELECT 1")
assert result == 1
Example: Using Langfuse Container
import pytest
@pytest.mark.container
@pytest.mark.langfuse
def test_langfuse_tracing(langfuse_config, langfuse_container, http_client):
"""Test with real Langfuse instance."""
from mcp_hangar.infrastructure.observability.langfuse_adapter import (
LangfuseObservabilityAdapter,
)
adapter = LangfuseObservabilityAdapter(langfuse_config)
span = adapter.start_tool_span("test", "tool", {"arg": 1})
span.end_success({"result": "ok"})
adapter.flush()
# Query Langfuse API
response = http_client.get(
f"{langfuse_container['url']}/api/public/traces",
auth=(langfuse_container["public_key"], langfuse_container["secret_key"]),
)
assert response.status_code == 200
Property-Based Testing
MCP Hangar uses Hypothesis for property-based testing.
Running Property Tests
# All property tests
pytest tests/unit/observability/test_property_based.py -v
# With more examples
pytest tests/unit/observability/test_property_based.py -v --hypothesis-seed=12345
Example Property Test
from hypothesis import given, strategies as st
@given(
mcp_server_name=st.text(min_size=1, max_size=50),
tool_name=st.text(min_size=1, max_size=50),
)
def test_adapter_accepts_any_strings(mcp_server_name, tool_name):
"""Adapter accepts any valid string inputs."""
adapter = NullObservabilityAdapter()
span = adapter.start_tool_span(mcp_server_name, tool_name, {})
assert isinstance(span, NullSpanHandle)
Container Tests
# Build images first
podman build -t localhost/mcp-sqlite -f docker/Dockerfile.sqlite .
# Prepare data directory
mkdir -p data && chmod 777 data
# Run tests
pytest tests/feature/ -v
Manual Testing
Subprocess MCP Server
# config.yaml
mcp_servers:
math:
mode: subprocess
command: [python, tests/mock_mcp_server.py]
python -m mcp_hangar.server
Test via Python
from mcp_hangar.domain.model import McpServer
mcp_server = McpServer(
mcp_server_id="test",
mode="subprocess",
command=["python", "tests/mock_mcp_server.py"]
)
mcp_server.ensure_ready()
result = mcp_server.invoke_tool("add", {"a": 5, "b": 3})
print(result) # {"result": 8}
mcp_server.shutdown()
Test MCP Server Directly
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | python tests/mock_mcp_server.py
Common Issues
MCP Server won't start
# Test directly
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | python tests/mock_mcp_server.py
Permission denied (container)
mcp_servers:
memory:
mode: container
read_only: false
volumes:
- "./data:/app/data:rw"
Tests hang
pytest tests/ -v --timeout=60