# Plugin Testing fapilog ships testing utilities for every plugin type so you can validate behavior quickly and catch contract drift. ## Installation Basic utilities (mocks, validators, factories, benchmarks) are included in the main package. For pytest fixtures, install the testing extra: ```bash pip install fapilog[testing] ``` ## Quick Start ```python import pytest from fapilog.testing import MockSink, validate_sink @pytest.mark.asyncio async def test_my_sink_contract() -> None: sink = MockSink() result = validate_sink(sink) result.raise_if_invalid() ``` ## Config Validation Plugins now parse configuration with `parse_plugin_config`, so tests can feed dicts directly and rely on Pydantic for coercion and typo checks. ```python from fapilog.plugins.redactors.field_mask import FieldMaskRedactor from fapilog.plugins.sinks.webhook import WebhookSink # Dict config is validated and coerced redactor = FieldMaskRedactor(config={"fields_to_mask": ["secret"], "max_depth": "5"}) sink = WebhookSink(config={"endpoint": "https://hooks.example.com", "batch_size": "2"}) ``` ## Mock Plugins - **MockSink**: captures written events, tracks lifecycle calls, optional latency/failure injection (`MockSinkConfig`). - **MockEnricher**: returns configured fields and tracks calls (`MockEnricherConfig`). - **MockRedactor**: masks configured fields with deep copy handling (`MockRedactorConfig`). - **MockProcessor**: echoes bytes, tracking processed views. - **MockFilter**: drops events by level or probability with optional failure injection (`MockFilterConfig`). Example filter usage: ```python from fapilog.testing import MockFilter, MockFilterConfig filter_plugin = MockFilter( MockFilterConfig(drop_levels=["DEBUG", "TRACE"], drop_rate=0.1) ) await filter_plugin.start() event = {"level": "INFO", "message": "kept"} assert await filter_plugin.filter(event) == event await filter_plugin.stop() ``` ## Validators Use validators to ensure plugins satisfy protocol contracts: - `validate_sink`, `validate_enricher`, `validate_redactor`, `validate_processor`, `validate_filter` - `validate_plugin_lifecycle` to exercise `start()`/`stop()` without changing behavior. ```python from fapilog.testing import validate_filter class MyFilter: name = "my_filter" async def filter(self, event: dict): ... validate_filter(MyFilter()).raise_if_invalid() ``` ### Required vs optional methods Validators treat lifecycle and health checks as optional and surface warnings when a method exists but is not async. Core methods remain required: - **Sinks:** required `write`; optional `start`, `stop`, `health_check`, `write_serialized` - **Enrichers:** required `enrich`; optional `start`, `stop`, `health_check` - **Redactors:** required `redact`; optional `start`, `stop`, `health_check` - **Processors:** required `process`; optional `start`, `stop`, `health_check`, `process_many` - **Filters:** required `filter`; optional `start`, `stop`, `health_check` (defaults to healthy when absent) Missing `health_check` yields a warning (not an error); validators assume `True` when it is not implemented. ## pytest Fixtures Load fixtures via pytest's plugin mechanism: ```python # conftest.py pytest_plugins = ("fapilog.testing.fixtures",) ``` Available fixtures: `mock_sink`, `mock_enricher`, `mock_redactor`, `mock_processor`, `mock_filter`, and `started_mock_sink` (async, cleans up automatically). Assertion helpers raise `ProtocolViolationError` when contracts break: - `assert_valid_sink`, `assert_valid_enricher`, `assert_valid_redactor`, `assert_valid_processor`, `assert_valid_filter` ## Benchmarks Measure plugin performance with lightweight benchmarks: - `benchmark_async` for generic async call benchmarking - `benchmark_sink`, `benchmark_enricher`, `benchmark_filter` helpers that manage lifecycle - `BenchmarkResult` provides `ops_per_second`, latency metrics, and a readable `__str__` ```python from fapilog.testing import MockSink from fapilog.testing import benchmark_sink result = await benchmark_sink(MockSink(), iterations=200, warmup=10) print(result) # sink:mock: 50000 ops/s, avg=0.020ms ``` ## CI/CD Integration - Run validators alongside unit tests to catch contract regressions early. - Use fixtures for smoke tests that exercise pipelines without hitting external systems. - Add a lightweight benchmark job to watch for performance regressions in custom plugins. - Gate merges on `ProtocolViolationError`-free results to keep plugins compatible across releases.