Simple testing of RESTful APIs
Deep integration with pytest providing custom hooks, CLI options, automatic test file discovery, and seamless integration with pytest plugins and reporting systems.
Core pytest plugin hooks that integrate Tavern into the pytest framework, enabling automatic test discovery and execution.
def pytest_addhooks(pluginmanager):
"""
Register custom Tavern hooks with pytest.
Parameters:
- pluginmanager: Pytest plugin manager instance
"""
def pytest_addoption(parser):
"""
Add Tavern-specific command line options to pytest.
Parameters:
- parser: Pytest argument parser
"""
def pytest_collect_file(parent, path):
"""
Collect Tavern YAML test files during pytest discovery.
Parameters:
- parent: Parent pytest node
- path: File path being considered for collection
Returns:
TavernFile instance if file matches Tavern pattern, None otherwise
"""Programmatic interface for adding Tavern CLI options to pytest parsers.
def add_parser_options(parser_addoption, with_defaults=True):
"""
Add Tavern CLI options to a parser programmatically.
Parameters:
- parser_addoption: Parser add_option function
- with_defaults: Whether to include default values
"""Interface for calling custom Tavern hooks during test execution.
def call_hook(test_block_config, hookname, **kwargs):
"""
Call a custom Tavern hook during test execution.
Parameters:
- test_block_config: Test configuration object
- hookname: Name of hook to call
- **kwargs: Arguments to pass to hook
"""Custom hooks for extending Tavern test execution with user-defined functionality.
def pytest_tavern_beta_before_every_test_run(
test_dict: dict,
variables: dict
) -> None:
"""
Hook called before each test execution.
Parameters:
- test_dict: Test definition dictionary
- variables: Current test variables
"""
def pytest_tavern_beta_after_every_test_run(
test_dict: dict,
variables: dict
) -> None:
"""
Hook called after each test execution.
Parameters:
- test_dict: Test definition dictionary
- variables: Updated test variables
"""
def pytest_tavern_beta_after_every_response(
expected: Any,
response: Any
) -> None:
"""
Hook called after each response is received.
Parameters:
- expected: Expected response specification
- response: Actual response object
"""
def pytest_tavern_beta_before_every_request(
request_args: MutableMapping
) -> None:
"""
Hook called before each request is sent.
Parameters:
- request_args: Mutable request arguments dictionary
"""Command line options added to pytest for controlling Tavern behavior.
Core Configuration:
--tavern-global-cfg: Path to global configuration file--tavern-strict: Response matching strictness level--tavern-file-path-regex: Regex pattern for test files (default: r".+\.tavern\.ya?ml$")Backend Selection:
--tavern-http-backend: HTTP backend plugin name (default: "requests")--tavern-mqtt-backend: MQTT backend plugin name (default: "paho-mqtt")--tavern-grpc-backend: gRPC backend plugin name (default: "grpc")Debugging Options:
--tavern-use-default-traceback: Use Python-style tracebacks--tavern-setup-init-logging: Setup simple logger for initializationHTTP-Specific:
--tavern-always-follow-redirects: Always follow HTTP redirects# Run Tavern tests with pytest
pytest tests/ -v
# Run specific Tavern test file
pytest test_api.tavern.yaml -v
# Run with Tavern-specific options
pytest tests/ --tavern-global-cfg config.yaml --tavern-strict# conftest.py
def pytest_tavern_beta_before_every_test_run(test_dict, variables):
"""Add custom setup before each test."""
print(f"Running test: {test_dict.get('test_name', 'Unknown')}")
# Add dynamic variables
variables['timestamp'] = str(int(time.time()))
variables['test_id'] = str(uuid.uuid4())
def pytest_tavern_beta_after_every_response(expected, response):
"""Log response details."""
if hasattr(response, 'status_code'):
print(f"Response status: {response.status_code}")
def pytest_tavern_beta_before_every_request(request_args):
"""Modify requests before sending."""
# Add common headers
if 'headers' not in request_args:
request_args['headers'] = {}
request_args['headers']['X-Test-Run'] = 'true'import argparse
from tavern._core.pytest import add_parser_options
# Create custom parser with Tavern options
parser = argparse.ArgumentParser()
add_parser_options(parser.add_argument, with_defaults=True)
# Parse arguments
args = parser.parse_args([
'--tavern-global-cfg', 'config.yaml',
'--tavern-strict',
'--tavern-http-backend', 'custom_requests'
])# Custom pytest plugin using Tavern collection
class CustomTavernPlugin:
def pytest_collect_file(self, parent, path):
"""Collect custom Tavern files."""
if path.ext == ".yaml" and "api_test" in path.basename:
# Use Tavern's collection logic
from tavern._core.pytest import pytest_collect_file
return pytest_collect_file(parent, path)# pytest.ini
[tool:pytest]
addopts =
--tavern-global-cfg=tests/global_config.yaml
--tavern-strict
--tavern-http-backend=requests
-v
testpaths = tests/
python_files = *.tavern.yaml# Plugin with comprehensive hook usage
class TavernTestPlugin:
def __init__(self):
self.test_results = []
self.start_time = None
def pytest_tavern_beta_before_every_test_run(self, test_dict, variables):
self.start_time = time.time()
# Set up test-specific configuration
test_name = test_dict.get('test_name', 'unknown')
variables['test_start_time'] = self.start_time
variables['test_environment'] = os.getenv('TEST_ENV', 'local')
print(f"Starting test: {test_name}")
def pytest_tavern_beta_after_every_test_run(self, test_dict, variables):
duration = time.time() - self.start_time
test_name = test_dict.get('test_name', 'unknown')
self.test_results.append({
'name': test_name,
'duration': duration,
'variables_used': list(variables.keys())
})
print(f"Completed test: {test_name} in {duration:.2f}s")
def pytest_tavern_beta_before_every_request(self, request_args):
# Add request tracing
if 'headers' not in request_args:
request_args['headers'] = {}
request_args['headers']['X-Trace-ID'] = str(uuid.uuid4())
# Log outgoing requests
method = request_args.get('method', 'GET')
url = request_args.get('url', 'unknown')
print(f"Sending {method} request to {url}")
def pytest_tavern_beta_after_every_response(self, expected, response):
# Validate response timing
if hasattr(response, 'elapsed'):
if response.elapsed.total_seconds() > 5.0:
print(f"WARNING: Slow response ({response.elapsed.total_seconds():.2f}s)")
# Log response status
if hasattr(response, 'status_code'):
print(f"Received response: {response.status_code}")# Works with pytest-xdist for parallel execution
pytest tests/ -n auto --tavern-global-cfg config.yaml
# Works with pytest-cov for coverage
pytest tests/ --cov=myapp --tavern-strict
# Works with pytest-html for HTML reports
pytest tests/ --html=report.html --self-contained-html# Internal classes used by pytest integration
class TavernFile:
"""Represents a Tavern YAML test file in pytest collection."""
class TavernItem:
"""Represents an individual Tavern test case in pytest."""from typing import Any, MutableMapping
from _pytest.config import Config
from _pytest.nodes import File, Item
TestConfig = "tavern._core.pytest.config.TestConfig"Install with Tessl CLI
npx tessl i tessl/pypi-tavern