Testing¶
This guide covers the testing strategy, structure, and practices for azure-functions-logging.
Running Tests¶
All Tests¶
Or using Hatch:
Both commands run the full test suite via pytest.
With Coverage¶
Or:
This generates a coverage report showing line-by-line coverage for the entire package.
Specific Test File¶
Tests Matching a Pattern¶
Verbose Output¶
Test Structure¶
All tests reside in the tests/ directory:
tests/
-- test_context.py # Context injection, binding, and ContextFilter tests
-- test_formatter.py # ColorFormatter tests
-- test_host_config.py # host.json conflict detection tests
-- test_integration.py # End-to-end integration tests
-- test_json_formatter.py # JsonFormatter tests
-- test_logger.py # FunctionLogger wrapper tests
-- test_setup.py # Logging setup and configuration tests
test_setup.py¶
Tests for the setup_logging() function and overall logging system configuration:
- Default setup creates a handler with
ColorFormatter format="json"creates a handler withJsonFormatter- Idempotent setup (multiple calls do not add duplicate handlers)
- Custom log level configuration
- Invalid format raises
ValueError
test_formatter.py¶
Tests for ColorFormatter:
- Format string structure:
HH:MM:SS LEVEL logger message - Color codes per level (DEBUG gray, INFO blue, WARNING yellow, ERROR red, CRITICAL bold red)
- Context fields appended when present
- Exception formatting with readable stack traces
- Cold start indicator in output
test_json_formatter.py¶
Tests for JsonFormatter:
- Output is valid JSON (single line per record)
- All required fields present: timestamp, level, logger, message
- Context fields included when set: invocation_id, function_name, trace_id, cold_start
- Extra fields from
bind()and keyword arguments included inextraobject - Exception traceback included in
exceptionfield - Timestamp format is ISO 8601
test_context.py¶
Tests for context injection, binding, and the ContextFilter:
inject_context tests:
- Extracts invocation_id, function_name, trace_id from context object
- Trace ID extracted from W3C traceparent header (second field of
trace_parent) - Cold start detection (first call returns True, subsequent calls return False)
- Handles missing attributes gracefully (defaults to None)
- Handles None context without raising
- Thread safety via contextvars
ContextFilter tests:
- Filter attaches context fields to LogRecord
- Filter works with no context set (fields default to None)
- Filter does not raise on any input
test_logger.py¶
Tests for the FunctionLogger wrapper class:
- All standard log methods delegate to the wrapped logger (debug, info, warning, error, critical, exception)
bind()returns a newFunctionLoggerwith merged context- Binding is immutable (original logger is not modified)
- Cumulative binding (bind on a bound logger combines both contexts)
clear_context()removes all bound fieldssetLevel(),isEnabledFor(),getEffectiveLevel()delegate correctlynameproperty returns the underlying logger name
test_host_config.py¶
Tests for host.json conflict detection:
- Warns when host.json logLevel is more restrictive than configured level
- No warning when host.json logLevel matches or is less restrictive
- No warning when host.json is missing
- Handles malformed host.json gracefully
test_integration.py¶
End-to-end integration tests:
- Full logging pipeline with ColorFormatter produces expected output
- Full logging pipeline with JsonFormatter produces valid JSON with all fields
- Context injection and log output work together correctly
Writing Tests¶
Test File Naming¶
Test files follow the test_*.py naming convention. Each test file corresponds to a logical area of the library.
Test Function Naming¶
Use descriptive names that explain the expected behavior:
def test_setup_logging_with_json_format_creates_json_formatter():
...
def test_inject_context_with_missing_attributes_defaults_to_none():
...
def test_cold_start_is_true_only_on_first_invocation():
...
Test Structure¶
import logging
import pytest
from azure_functions_logging import setup_logging, get_logger, inject_context
def test_basic_logging_setup():
"""setup_logging() configures the root logger with a handler."""
# Arrange
setup_logging()
# Act
logger = get_logger(__name__)
logger.info("test message")
# Assert
root = logging.getLogger()
assert len(root.handlers) >= 1
Testing Context¶
When testing context injection, create a mock context object:
class MockContext:
invocation_id = "test-invocation-id"
function_name = "TestFunction"
class trace_context:
trace_parent = "00-abcdef1234567890abcdef1234567890-1234567890abcdef-01"
def test_inject_context_sets_fields():
inject_context(MockContext())
logger = get_logger(__name__)
# Verify context fields appear in output
Testing Formatters¶
Capture log output using a StringIO handler:
import io
import logging
def test_json_formatter_output():
stream = io.StringIO()
handler = logging.StreamHandler(stream)
handler.setFormatter(JsonFormatter())
logger = logging.getLogger("test")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.info("test message")
output = stream.getvalue()
data = json.loads(output)
assert data["message"] == "test message"
assert data["level"] == "INFO"
CI Test Matrix¶
Tests run automatically on every pull request and push to main. The CI matrix covers:
| Python Version | Status |
|---|---|
| 3.10 | Tested |
| 3.11 | Tested |
| 3.12 | Tested |
| 3.13 | Tested |
| 3.14 | Tested |
All tests must pass across the entire matrix before a pull request can be merged.
Coverage¶
Coverage is tracked via pytest-cov and reported to Codecov. The current coverage badge is visible in the project README.
To check coverage locally:
The report shows:
- Line coverage per module
- Branch coverage per module
- Uncovered lines highlighted
When adding new features, ensure coverage does not decrease. New code should have corresponding test coverage.
Test Best Practices¶
- Isolate tests: Each test should be independent and not rely on state from other tests
- Clean up: Reset logging handlers and contextvars after tests to avoid interference
- Use fixtures: Leverage pytest fixtures for common setup (mock contexts, loggers, handlers)
- Test edge cases: Missing attributes, None values, empty strings, concurrent access
- Test the public API: Focus on testing through
setup_logging(),get_logger(), andinject_context()rather than internal implementations - Regression tests: Every bug fix must include a test that would have caught the bug
Real Azure E2E Tests¶
The project includes a real Azure end-to-end test workflow that deploys an actual Function App to Azure and validates HTTP endpoints.
Workflow¶
- File:
.github/workflows/e2e-azure.yml - Trigger: Manual (
workflow_dispatch) or weekly schedule (Mondays 02:00 UTC) - Infrastructure: Azure Consumption plan,
koreacentralregion - Cleanup: Resource group deleted immediately after tests (
if: always())
Running E2E Tests¶
Required Secrets & Variables¶
| Name | Type | Description |
|---|---|---|
AZURE_CLIENT_ID |
Secret | App Registration Client ID (OIDC) |
AZURE_TENANT_ID |
Secret | Azure Tenant ID |
AZURE_SUBSCRIPTION_ID |
Secret | Azure Subscription ID |
AZURE_LOCATION |
Variable | Azure region (default: koreacentral) |
Test Report¶
HTML test report is uploaded as a GitHub Actions artifact (retained 30 days).