Testing
TL;DR
Use TestClient to test skill commands without running the full CLI. It supports both keyword-based (client.run) and CLI-style (client.run_cli) invocation. Combine with pytest-asyncio for async tests.
Setup
Add dev dependencies to pyproject.toml:
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.24",
"pytest-cov>=6.0",
"ruff>=0.8",
]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]Install:
pip install -e '.[dev]'TestClient
from cmdop_skill import TestClient
from my_skill._skill import skill
client = TestClient(skill)Keyword invocation
Pass arguments as Python keyword arguments:
async def test_check_domain():
client = TestClient(skill)
result = await client.run("check", domain="github.com", timeout=5)
assert result["ok"] is True
assert result["domain"] == "github.com"
assert result["days_left"] > 0CLI-style invocation
Pass arguments as strings, exactly like the command line:
async def test_check_cli():
client = TestClient(skill)
result = await client.run_cli("check", "--domain", "github.com", "--timeout", "5")
assert result["ok"] is TrueContext manager
Use async with to ensure teardown hooks run:
async def test_with_lifecycle():
async with TestClient(skill) as client:
result = await client.run("check", domain="github.com")
assert result["ok"] is True
# teardown has run at this pointTest patterns
Error cases
async def test_invalid_domain():
client = TestClient(skill)
result = await client.run("check", domain="not-a-domain")
assert result["ok"] is False
assert "error" in resultMocking external calls
from unittest.mock import AsyncMock, patch
async def test_with_mock():
client = TestClient(skill)
with patch("my_skill._skill.fetch_cert", new_callable=AsyncMock) as mock:
mock.return_value = {"days_left": 42, "issuer": "Let's Encrypt"}
result = await client.run("check", domain="example.com")
assert result["days_left"] == 42
mock.assert_called_once_with("example.com", timeout=10)Shared fixtures
In tests/conftest.py:
import pytest
from cmdop_skill import TestClient
from my_skill._skill import skill
@pytest.fixture
def client():
return TestClient(skill)Then in tests:
async def test_check(client):
result = await client.run("check", domain="github.com")
assert result["ok"] is TrueRunning tests
make test # via Makefile
pytest tests/ -v # directly
pytest tests/ -v --cov=src # with coverageLast updated on