testing/pytest-testing-skill/SKILL.md
Set up pytest 8.x with Python for unit and integration testing using fixtures (scope, autouse, parametrize), async tests (pytest-asyncio), mocking (unittest.mock, pytest-mock), coverage (pytest-cov), conftest.py patterns, and markers.
npx skillsauth add achreftlili/deep-dev-skills pytest-testing-skillInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Set up pytest 8.x with Python for unit and integration testing using fixtures (scope, autouse, parametrize), async tests (pytest-asyncio), mocking (unittest.mock, pytest-mock), coverage (pytest-cov), conftest.py patterns, and markers.
pip install pytest pytest-asyncio pytest-mock pytest-cov
# Optional plugins
pip install pytest-xdist # Parallel test execution
pip install pytest-randomly # Randomize test order
pip install factory-boy # Test data factories
pip install httpx # Async HTTP client for testing FastAPI
app/
__init__.py
main.py
services/
user_service.py
repositories/
user_repository.py
tests/
__init__.py
conftest.py # Root-level fixtures and configuration
unit/
__init__.py
conftest.py # Unit-test-specific fixtures
test_user_service.py
integration/
__init__.py
conftest.py # Integration-test-specific fixtures (DB, API client)
test_user_api.py
factories.py # Test data factories
pyproject.toml # pytest configuration
test_. Test functions start with test_.conftest.py files for shared fixtures. Place them at the appropriate directory level (root for global, subdirectory for scoped).setUp/tearDown methods.@pytest.mark.parametrize for data-driven tests instead of duplicating test functions.@pytest.mark.asyncio for async test functions.@pytest.mark.slow, @pytest.mark.integration).pyproject.toml)[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
]
addopts = [
"--strict-markers",
"--strict-config",
"-ra",
"--tb=short",
]
filterwarnings = [
"error",
"ignore::DeprecationWarning",
]
tests/conftest.py)import pytest
from unittest.mock import AsyncMock
@pytest.fixture
def mock_user():
"""A sample user dict for testing."""
return {
"id": 1,
"email": "[email protected]",
"name": "Test User",
"role": "user",
}
@pytest.fixture
def mock_users(mock_user):
"""A list of sample users."""
return [
mock_user,
{"id": 2, "email": "[email protected]", "name": "Admin User", "role": "admin"},
]
tests/unit/test_user_service.py)import pytest
from unittest.mock import AsyncMock, patch
from app.services.user_service import UserService
class TestUserService:
@pytest.fixture(autouse=True)
def setup(self):
self.mock_repo = AsyncMock()
self.service = UserService(repository=self.mock_repo)
async def test_get_user_returns_user_when_found(self, mock_user):
self.mock_repo.find_by_id.return_value = mock_user
result = await self.service.get_user(1)
assert result == mock_user
self.mock_repo.find_by_id.assert_called_once_with(1)
async def test_get_user_raises_when_not_found(self):
self.mock_repo.find_by_id.return_value = None
with pytest.raises(ValueError, match="User not found"):
await self.service.get_user(999)
async def test_create_user_hashes_password(self):
self.mock_repo.create.return_value = {"id": 1, "email": "[email protected]"}
await self.service.create_user(
email="[email protected]",
name="New User",
password="plaintext123",
)
call_args = self.mock_repo.create.call_args[1]
assert call_args["password"] != "plaintext123"
import pytest
from app.utils.validators import validate_email
@pytest.mark.parametrize(
"email, expected",
[
("[email protected]", True),
("[email protected]", True),
("invalid", False),
("@no-local.com", False),
("no-at-sign.com", False),
("", False),
],
ids=[
"valid-basic",
"valid-subdomain",
"no-at-sign-no-domain",
"no-local-part",
"missing-at",
"empty-string",
],
)
def test_validate_email(email: str, expected: bool):
assert validate_email(email) is expected
import pytest
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
@pytest.fixture(scope="session")
def engine():
"""Create engine once for the entire test session."""
engine = create_async_engine("postgresql+asyncpg://test:test@localhost:5432/testdb")
yield engine
# Cleanup runs after all tests complete
@pytest.fixture(scope="function")
async def session(engine):
"""Create a new session per test, rolled back after each test."""
async_session = async_sessionmaker(engine, class_=AsyncSession)
async with async_session() as session:
async with session.begin():
yield session
await session.rollback()
def test_send_notification_calls_email_service(mocker):
mock_send = mocker.patch("app.services.email_service.send_email", return_value=True)
mocker.patch("app.services.email_service.get_template", return_value="<html>Hello</html>")
from app.services.notification_service import notify_user
notify_user("[email protected]", "Welcome!")
mock_send.assert_called_once_with(
to="[email protected]",
subject="Welcome!",
body="<html>Hello</html>",
)
import pytest
import httpx
from app.main import app # FastAPI app
@pytest.fixture
async def client():
"""Async HTTP client for FastAPI integration tests."""
async with httpx.AsyncClient(app=app, base_url="http://test") as client:
yield client
class TestUserAPI:
async def test_create_user(self, client: httpx.AsyncClient):
response = await client.post(
"/api/users",
json={"email": "[email protected]", "name": "New User", "password": "secret123"},
)
assert response.status_code == 201
data = response.json()
assert data["email"] == "[email protected]"
assert "password" not in data
async def test_get_user_not_found(self, client: httpx.AsyncClient):
response = await client.get("/api/users/99999")
assert response.status_code == 404
assert response.json()["detail"] == "User not found"
tests/factories.py)import factory
from app.models.user import User
class UserFactory(factory.Factory):
class Meta:
model = User
id = factory.Sequence(lambda n: n + 1)
email = factory.LazyAttribute(lambda obj: f"user{obj.id}@example.com")
name = factory.Faker("name")
role = "user"
# Usage in tests:
# user = UserFactory()
# admin = UserFactory(role="admin", email="[email protected]")
# users = UserFactory.build_batch(5)
import pytest
@pytest.mark.slow
def test_complex_computation():
"""This test takes a long time."""
result = expensive_computation()
assert result > 0
@pytest.mark.integration
async def test_database_query(session):
"""This test requires a real database."""
users = await session.execute(select(User))
assert users.scalars().all() is not None
tests/integration/conftest.py)import pytest
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from app.database import Base
from app.models import * # noqa: ensure models are loaded
@pytest.fixture(scope="session")
async def engine():
engine = create_async_engine("postgresql+asyncpg://test:test@localhost:5432/testdb")
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield engine
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await engine.dispose()
@pytest.fixture
async def session(engine):
session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with session_factory() as session:
yield session
await session.rollback()
responses library)import responses
@responses.activate
def test_external_api_call():
responses.add(
responses.GET,
"https://api.example.com/users",
json=[{"id": 1, "name": "Alice"}],
status=200,
)
result = fetch_users() # Your function that calls the external API
assert len(result) == 1
assert result[0]["name"] == "Alice"
@responses.activate
def test_external_api_error():
responses.add(
responses.GET,
"https://api.example.com/users",
json={"error": "Service unavailable"},
status=503,
)
with pytest.raises(ExternalServiceError):
fetch_users()
# Install the responses library
pip install responses
# Run all tests
pytest
# Run with verbose output
pytest -v
# Run a specific file
pytest tests/unit/test_user_service.py
# Run a specific test function
pytest tests/unit/test_user_service.py::TestUserService::test_get_user_returns_user_when_found
# Run tests matching a keyword
pytest -k "test_create"
# Run tests by marker
pytest -m "not slow"
pytest -m integration
# Run with coverage
pytest --cov=app --cov-report=term-missing --cov-report=html
# Run in parallel (pytest-xdist)
pytest -n auto
# Run with detailed failure output
pytest --tb=long
# Stop on first failure
pytest -x
# Re-run only failed tests
pytest --lf
# Show slowest 10 tests
pytest --durations=10
# Generate JUnit XML report (for CI)
pytest --junitxml=report.xml
httpx.AsyncClient(app=app) for async integration tests. Override dependencies with app.dependency_overrides for mocking database sessions.sqlalchemy-starter skill. Use session-scoped engine fixture and function-scoped session with rollback.github-actions-ci skill. Run pytest --cov --junitxml=report.xml in CI. Upload coverage artifacts.docker compose to spin up test databases. Set DATABASE_URL as an environment variable in CI.mypy. Run mypy separately from pytest (they serve different purposes).# docker-compose.test.yml
services:
test-db:
image: postgres:16-alpine
environment:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- "5433:5432"
tmpfs:
- /var/lib/postgresql/data # RAM-backed for speed
Use DATABASE_URL=postgresql+asyncpg://test:test@localhost:5433/testdb in your test environment. Start with docker compose -f docker-compose.test.yml up -d before running integration tests.
testing
Set up Vitest 2.x with TypeScript for unit and component testing using test/describe/it, vi.fn/vi.mock/vi.spyOn, component testing with Testing Library, coverage (v8/istanbul), workspace config, and snapshot testing.
testing
Set up Playwright 1.49+ with TypeScript for E2E testing using page object model, fixtures, test.describe/test blocks, assertions, selectors, network mocking, CI configuration, and trace viewer.
testing
Set up Jest 30+ with TypeScript for unit tests, integration tests, mocking (jest.fn, jest.mock, jest.spyOn), coverage configuration, custom matchers, snapshot testing, and setup/teardown patterns.
tools
--- name: react-native-project-starter description: > Scaffold a production-ready React Native 0.76+ app with Expo SDK 52+, Expo Router for file-based navigation, Zustand for state management, TypeScript, EAS Build, platform-specific code, and AsyncStorage. category: mobile agent-type: coding compatibility: Node.js >= 20.x, npm >= 10.x, Expo CLI, EAS CLI, iOS: Xcode 16+, Android: Android Studio with SDK 35+ --- # React Native Project Starter > Scaffold a production-ready React Native 0.7