Mocking Strategies & Test Doubles
Note
Problem-solving guide for mocking HoneyHive SDK components
Practical solutions for creating test doubles, mocks, and stubs to isolate your code under test and control external dependencies.
Mocking allows you to test your code in isolation by replacing external dependencies with controlled test doubles. This is essential for reliable, fast unit tests.
Quick Start
Problem: I need to mock HoneyHive SDK to test my application without making real API calls.
Solution:
from unittest.mock import Mock, patch
import pytest
def test_with_mocked_honeyhive():
"""Quick example of mocking HoneyHive SDK."""
with patch('honeyhive.HoneyHiveTracer') as mock_tracer_class:
# Configure mock
mock_tracer = Mock()
mock_span = Mock()
mock_span.__enter__ = Mock(return_value=mock_span)
mock_span.__exit__ = Mock(return_value=None)
mock_tracer.trace.return_value = mock_span
mock_tracer_class.init.return_value = mock_tracer
# Import and use your code that uses HoneyHive
from your_app import function_that_uses_honeyhive
result = function_that_uses_honeyhive("test_input")
# Verify interactions
mock_tracer_class.init.assert_called_once()
mock_tracer.trace.assert_called()
assert result is not None
Mock Tracer Creation
Problem: Create a comprehensive mock tracer for testing.
Solution - Mock Tracer Class:
"""Comprehensive mock tracer for HoneyHive SDK testing."""
from unittest.mock import Mock, MagicMock
from typing import Dict, Any, List, Optional
import time
import threading
class MockHoneyHiveTracer:
"""Mock implementation of HoneyHiveTracer for testing."""
def __init__(self, **kwargs):
self.api_key = kwargs.get("api_key", "mock-api-key")
self.project = kwargs.get("project", "mock-project")
self.source = kwargs.get("source", "mock-source")
self.session_name = kwargs.get("session_name", "mock-session")
self.test_mode = kwargs.get("test_mode", True)
self.session_id = f"mock-session-{int(time.time())}"
# Track all created spans
self.spans = []
self.events = []
self.flush_calls = []
self.close_calls = []
# Threading support
self._lock = threading.Lock()
def trace(self, name: str, **kwargs) -> 'MockSpan':
"""Create a mock span."""
span = MockSpan(name, tracer=self, **kwargs)
with self._lock:
self.spans.append(span)
return span
def start_span(self, name: str, **kwargs) -> 'MockSpan':
"""Start a mock span (alias for trace)."""
return self.trace(name, **kwargs)
def enrich_current_span(self, **kwargs):
"""Mock span enrichment."""
if self.spans:
current_span = self.spans[-1]
current_span.enrich(**kwargs)
def force_flush(self, timeout_millis: int = 5000) -> bool:
"""Mock force flush operation."""
with self._lock:
self.flush_calls.append({
"timeout_millis": timeout_millis,
"timestamp": time.time()
})
return True # Always successful in mock
def close(self):
"""Mock close operation."""
with self._lock:
self.close_calls.append({"timestamp": time.time()})
# Test utilities
def get_spans(self) -> List['MockSpan']:
"""Get all created spans for verification."""
with self._lock:
return self.spans.copy()
def get_span_by_name(self, name: str) -> Optional['MockSpan']:
"""Get span by name for verification."""
for span in self.spans:
if span.name == name:
return span
return None
def clear_spans(self):
"""Clear all recorded spans."""
with self._lock:
self.spans.clear()
self.events.clear()
def assert_span_created(self, name: str):
"""Assert that a span with given name was created."""
span = self.get_span_by_name(name)
assert span is not None, f"No span found with name: {name}"
return span
def assert_attribute_set(self, span_name: str, key: str, value: Any):
"""Assert that an attribute was set on a span."""
span = self.assert_span_created(span_name)
assert key in span.attributes, f"Attribute '{key}' not found in span '{span_name}'"
assert span.attributes[key] == value, f"Attribute '{key}' has value {span.attributes[key]}, expected {value}"
class MockSpan:
"""Mock implementation of a tracing span."""
def __init__(self, name: str, tracer: MockHoneyHiveTracer = None, **kwargs):
self.name = name
self.tracer = tracer
self.attributes = {}
self.events = []
self.exceptions = []
self.status = "OK"
self.start_time = time.time()
self.end_time = None
self.is_active = False
# Extract kwargs
self.event_type = kwargs.get("event_type")
self.event_name = kwargs.get("event_name")
def __enter__(self):
"""Context manager entry."""
self.is_active = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Context manager exit."""
self.is_active = False
self.end_time = time.time()
if exc_type:
self.record_exception(exc_val)
self.status = "ERROR"
return False # Don't suppress exceptions
def set_attribute(self, key: str, value: Any):
"""Set span attribute."""
self.attributes[key] = value
def get_attribute(self, key: str) -> Any:
"""Get span attribute."""
return self.attributes.get(key)
def record_exception(self, exception: Exception):
"""Record exception in span."""
self.exceptions.append({
"exception": exception,
"timestamp": time.time()
})
self.set_attribute("error.type", type(exception).__name__)
self.set_attribute("error.message", str(exception))
def add_event(self, name: str, attributes: Dict[str, Any] = None):
"""Add event to span."""
event = {
"name": name,
"attributes": attributes or {},
"timestamp": time.time()
}
self.events.append(event)
if self.tracer:
self.tracer.events.append(event)
def enrich(self, **kwargs):
"""Enrich span with additional data."""
for key, value in kwargs.items():
if key == "metadata" and isinstance(value, dict):
for meta_key, meta_value in value.items():
self.set_attribute(f"metadata.{meta_key}", meta_value)
elif key == "outputs" and isinstance(value, dict):
for output_key, output_value in value.items():
self.set_attribute(f"output.{output_key}", output_value)
else:
self.set_attribute(key, value)
def duration_ms(self) -> float:
"""Get span duration in milliseconds."""
if self.end_time:
return (self.end_time - self.start_time) * 1000
return (time.time() - self.start_time) * 1000
Using Mock Tracer:
def test_with_mock_tracer():
"""Example of using MockHoneyHiveTracer."""
# Create mock tracer
mock_tracer = MockHoneyHiveTracer(
api_key="test-key" )
# Use mock tracer in your code
with mock_tracer.trace("test-operation") as span:
span.set_attribute("test.value", "mock-test")
span.add_event("test-event", {"event_data": "test"})
# Verify interactions
mock_tracer.assert_span_created("test-operation")
mock_tracer.assert_attribute_set("test-operation", "test.value", "mock-test")
# Check events
spans = mock_tracer.get_spans()
assert len(spans) == 1
assert len(spans[0].events) == 1
assert spans[0].events[0]["name"] == "test-event"
Patching Strategies
Problem: Mock HoneyHive SDK at different levels of your application.
Solution - Comprehensive Patching Strategies:
"""Different strategies for patching HoneyHive SDK."""
import pytest
from unittest.mock import patch, Mock, MagicMock
# Strategy 1: Patch at module level
@patch('honeyhive.HoneyHiveTracer')
def test_module_level_patching(mock_tracer_class):
"""Patch the entire tracer class."""
mock_tracer = Mock()
mock_tracer_class.init.return_value = mock_tracer
# Your code that imports and uses HoneyHive
from your_app import initialize_tracing
tracer = initialize_tracing()
mock_tracer_class.init.assert_called_once()
# Strategy 2: Patch at import level
def test_import_level_patching():
"""Patch HoneyHive at import time."""
with patch.dict('sys.modules', {'honeyhive': Mock()}):
# Re-import your module with mocked honeyhive
import importlib
import your_app
importlib.reload(your_app)
# Test your app with mocked honeyhive
result = your_app.some_function()
assert result is not None
# Strategy 3: Patch specific methods
@patch('honeyhive.HoneyHiveTracer.init')
@patch('honeyhive.HoneyHiveTracer.trace')
def test_method_level_patching(mock_trace, mock_init):
"""Patch specific tracer methods."""
mock_tracer = Mock()
mock_init.return_value = mock_tracer
mock_span = Mock()
mock_span.__enter__ = Mock(return_value=mock_span)
mock_span.__exit__ = Mock(return_value=None)
mock_trace.return_value = mock_span
# Your code
from honeyhive import HoneyHiveTracer
tracer = HoneyHiveTracer.init(
api_key="test", # Or set HH_API_KEY environment variable
project="test-project", # Or set HH_PROJECT environment variable
test_mode=True # Or set HH_TEST_MODE=true
)
with tracer.trace("test") as span:
span.set_attribute("key", "value")
mock_init.assert_called_once()
mock_trace.assert_called_once_with("test")
# Strategy 4: Context manager patching
def test_context_manager_patching():
"""Use patch as context manager for fine control."""
with patch('honeyhive.HoneyHiveTracer') as mock_class:
mock_tracer = MockHoneyHiveTracer()
mock_class.init.return_value = mock_tracer
# Test specific behavior
result = your_function_that_uses_honeyhive()
# Verify specific interactions
assert mock_tracer.spans
assert result is not None
# Strategy 5: Decorator-based patching
class TestWithPatching:
"""Test class with decorator-based patching."""
@patch('honeyhive.HoneyHiveTracer')
def test_method1(self, mock_tracer):
"""Test with mocked tracer."""
mock_tracer.init.return_value = Mock()
# Test code here
@patch.object('honeyhive.HoneyHiveTracer', 'init')
def test_method2(self, mock_init):
"""Test with mocked init method."""
mock_init.return_value = MockHoneyHiveTracer()
# Test code here
Fixture-Based Mocking
Problem: Create reusable mock fixtures for consistent testing.
Solution - PyTest Fixtures:
"""PyTest fixtures for HoneyHive mocking."""
import pytest
from unittest.mock import Mock, patch
@pytest.fixture
def mock_tracer():
"""Fixture providing a mock HoneyHive tracer."""
return MockHoneyHiveTracer(
api_key="fixture-test-key", test_mode=True
)
@pytest.fixture
def mock_honeyhive_class():
"""Fixture that patches HoneyHiveTracer class."""
with patch('honeyhive.HoneyHiveTracer') as mock_class:
mock_tracer = MockHoneyHiveTracer()
mock_class.init.return_value = mock_tracer
mock_class.return_value = mock_tracer
yield mock_class
@pytest.fixture
def mock_honeyhive_init():
"""Fixture that patches HoneyHiveTracer.init method."""
with patch('honeyhive.HoneyHiveTracer.init') as mock_init:
mock_tracer = MockHoneyHiveTracer()
mock_init.return_value = mock_tracer
yield mock_tracer
@pytest.fixture
def mock_honeyhive_trace_method():
"""Fixture that patches the trace method specifically."""
with patch('honeyhive.HoneyHiveTracer.trace') as mock_trace:
mock_span = MockSpan("mocked-span")
mock_trace.return_value = mock_span
yield mock_trace
@pytest.fixture
def mock_honeyhive_decorators():
"""Fixture that patches HoneyHive decorators."""
with patch('honeyhive.trace') as mock_trace_decorator:
def trace_wrapper(func):
"""Mock trace decorator that just calls the function."""
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
mock_trace_decorator.side_effect = trace_wrapper
yield mock_trace_decorator
@pytest.fixture
def isolated_honeyhive():
"""Fixture that completely isolates HoneyHive imports."""
with patch.dict('sys.modules', {
'honeyhive': Mock(),
'honeyhive.tracer': Mock(),
'honeyhive.api': Mock(),
'honeyhive.evaluation': Mock()
}):
yield
Using Mock Fixtures:
def test_with_mock_tracer_fixture(mock_tracer):
"""Test using mock tracer fixture."""
# Use the mock tracer directly
with mock_tracer.trace("fixture-test") as span:
span.set_attribute("test.fixture", True)
# Verify using mock tracer utilities
mock_tracer.assert_span_created("fixture-test")
mock_tracer.assert_attribute_set("fixture-test", "test.fixture", True)
def test_with_mocked_class(mock_honeyhive_class):
"""Test with completely mocked HoneyHive class."""
from honeyhive import HoneyHiveTracer
tracer = HoneyHiveTracer.init(
api_key="test", # Or set HH_API_KEY environment variable
project="test-project", # Or set HH_PROJECT environment variable
test_mode=True # Or set HH_TEST_MODE=true
)
mock_honeyhive_class.init.assert_called_once_with(api_key="test")
def test_with_isolated_honeyhive(isolated_honeyhive):
"""Test with completely isolated HoneyHive."""
# HoneyHive is completely mocked, won't interfere with test
result = some_function_that_imports_honeyhive()
assert result is not None
Mocking External Dependencies
Problem: Mock external services that HoneyHive might interact with.
Solution - External Dependency Mocking:
"""Mocking external dependencies for HoneyHive testing."""
import pytest
from unittest.mock import Mock, patch, MagicMock
import requests
class MockHoneyHiveAPI:
"""Mock implementation of HoneyHive API."""
def __init__(self):
self.sessions = []
self.events = []
self.projects = []
self.call_log = []
def create_session(self, project: str, session_name: str = None):
"""Mock session creation."""
session = {
"session_id": f"mock-session-{len(self.sessions)}",
"project": project,
"session_name": session_name or f"session-{len(self.sessions)}",
"created_at": "2024-01-01T00:00:00Z"
}
self.sessions.append(session)
self.call_log.append(("create_session", session))
return session
def create_event(self, session_id: str, event_data: dict):
"""Mock event creation."""
event = {
"event_id": f"mock-event-{len(self.events)}",
"session_id": session_id,
**event_data,
"created_at": "2024-01-01T00:00:00Z"
}
self.events.append(event)
self.call_log.append(("create_event", event))
return event
def get_session(self, session_id: str):
"""Mock session retrieval."""
for session in self.sessions:
if session["session_id"] == session_id:
self.call_log.append(("get_session", session_id))
return session
return None
@pytest.fixture
def mock_api():
"""Fixture providing mock HoneyHive API."""
return MockHoneyHiveAPI()
@pytest.fixture
def mock_requests():
"""Fixture that mocks HTTP requests."""
with patch('requests.post') as mock_post:
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"status": "success"}
mock_post.return_value = mock_response
yield mock_post
@pytest.fixture
def mock_network_failure():
"""Fixture that simulates network failures."""
with patch('requests.post') as mock_post:
mock_post.side_effect = requests.ConnectionError("Network error")
yield mock_post
def test_with_mocked_api(mock_api, mock_requests):
"""Test with mocked API and network calls."""
# Configure requests mock to return API responses
def mock_post_response(url, **kwargs):
if "sessions" in url:
return Mock(
status_code=200,
json=lambda: mock_api.create_session("test-project")
)
elif "events" in url:
return Mock(
status_code=200,
json=lambda: mock_api.create_event("session-1", kwargs.get("json", {}))
)
return Mock(status_code=200, json=lambda: {})
mock_requests.side_effect = mock_post_response
# Test your code that uses HoneyHive API
from honeyhive import HoneyHiveTracer
tracer = HoneyHiveTracer.init(
api_key="test-key", test_mode=False # Use "real" API (which is mocked)
)
with tracer.trace("api-test") as span:
span.set_attribute("test.api", True)
# Verify API calls were made
assert len(mock_api.call_log) > 0
def test_network_failure_handling(mock_network_failure):
"""Test handling of network failures."""
from honeyhive import HoneyHiveTracer
# Should not raise exception even with network failure
tracer = HoneyHiveTracer.init(
api_key="test-key", test_mode=False
)
# Should handle gracefully
with tracer.trace("network-failure-test") as span:
span.set_attribute("test.network_failure", True)
# Verify network call was attempted
mock_network_failure.assert_called()
Mocking Async Operations
Problem: Mock async operations in HoneyHive SDK.
Solution - Async Mocking:
"""Mocking async operations for HoneyHive SDK."""
import asyncio
import pytest
from unittest.mock import AsyncMock, Mock, patch
class MockAsyncHoneyHiveTracer:
"""Mock async tracer for testing."""
def __init__(self, **kwargs):
self.api_key = kwargs.get("api_key", "mock-key")
self.project = kwargs.get("project", "mock-project")
self.spans = []
async def atrace(self, name: str, **kwargs):
"""Mock async trace method."""
span = MockSpan(name)
self.spans.append(span)
return span
async def force_flush(self, timeout_millis: int = 5000) -> bool:
"""Mock async flush operation."""
await asyncio.sleep(0.01) # Simulate async work
return True
async def close(self):
"""Mock async close operation."""
await asyncio.sleep(0.01) # Simulate cleanup
@pytest.fixture
def mock_async_tracer():
"""Fixture providing mock async tracer."""
return MockAsyncHoneyHiveTracer()
@pytest.fixture
def mock_async_honeyhive():
"""Fixture that patches async HoneyHive operations."""
with patch('honeyhive.atrace') as mock_atrace:
async_mock = AsyncMock()
mock_atrace.return_value = async_mock
yield mock_atrace
@pytest.mark.asyncio
async def test_async_operations(mock_async_tracer):
"""Test async operations with mock tracer."""
# Test async trace
span = await mock_async_tracer.atrace("async-test")
assert span.name == "async-test"
# Test async flush
flush_result = await mock_async_tracer.force_flush()
assert flush_result is True
# Test async close
await mock_async_tracer.close()
@pytest.mark.asyncio
async def test_with_async_mock_decorator(mock_async_honeyhive):
"""Test with async decorator mocking."""
from honeyhive import atrace
@atrace(event_type="async_test")
async def async_function():
await asyncio.sleep(0.01)
return "async_result"
result = await async_function()
assert result == "async_result"
mock_async_honeyhive.assert_called()
Advanced Mocking Patterns
Problem: Implement sophisticated mocking patterns for complex scenarios.
Solution - Advanced Patterns:
"""Advanced mocking patterns for complex testing scenarios."""
from unittest.mock import Mock, MagicMock, PropertyMock, call
from contextlib import contextmanager
import time
class StatefulMockTracer:
"""Mock tracer that maintains state across calls."""
def __init__(self):
self.state = "initialized"
self.spans = []
self.call_count = 0
self.errors = []
def trace(self, name: str, **kwargs):
"""Stateful trace method."""
self.call_count += 1
if self.state == "error_mode":
raise Exception(f"Simulated error for span: {name}")
span = MockSpan(name)
self.spans.append(span)
# Simulate state changes
if self.call_count > 10:
self.state = "rate_limited"
return span
def set_error_mode(self, enabled: bool = True):
"""Set tracer to error mode for testing error handling."""
self.state = "error_mode" if enabled else "normal"
def reset(self):
"""Reset tracer state."""
self.state = "initialized"
self.spans.clear()
self.call_count = 0
self.errors.clear()
class ConditionalMockTracer:
"""Mock tracer with conditional behavior."""
def __init__(self):
self.conditions = {}
self.default_behavior = lambda name, **kwargs: MockSpan(name)
def add_condition(self, span_name: str, behavior):
"""Add conditional behavior for specific span names."""
self.conditions[span_name] = behavior
def trace(self, name: str, **kwargs):
"""Trace with conditional behavior."""
if name in self.conditions:
return self.conditions[name](name, **kwargs)
return self.default_behavior(name, **kwargs)
def test_stateful_mocking():
"""Test with stateful mock tracer."""
mock_tracer = StatefulMockTracer()
# Normal operation
span1 = mock_tracer.trace("test-1")
assert span1.name == "test-1"
assert mock_tracer.state == "initialized"
# Set error mode
mock_tracer.set_error_mode(True)
with pytest.raises(Exception, match="Simulated error"):
mock_tracer.trace("test-error")
# Reset and continue
mock_tracer.reset()
span2 = mock_tracer.trace("test-2")
assert span2.name == "test-2"
def test_conditional_mocking():
"""Test with conditional mock behavior."""
mock_tracer = ConditionalMockTracer()
# Add specific behavior for certain spans
def slow_span_behavior(name, **kwargs):
span = MockSpan(name)
span.set_attribute("performance.slow", True)
return span
def error_span_behavior(name, **kwargs):
raise Exception(f"Error in {name}")
mock_tracer.add_condition("slow-operation", slow_span_behavior)
mock_tracer.add_condition("error-operation", error_span_behavior)
# Test normal span
normal_span = mock_tracer.trace("normal-operation")
assert normal_span.name == "normal-operation"
# Test slow span
slow_span = mock_tracer.trace("slow-operation")
assert slow_span.get_attribute("performance.slow") is True
# Test error span
with pytest.raises(Exception, match="Error in error-operation"):
mock_tracer.trace("error-operation")
class MockTracerBuilder:
"""Builder pattern for creating configured mock tracers."""
def __init__(self):
self.mock_tracer = Mock()
self.spans_config = {}
self.global_config = {}
def with_span(self, name: str, attributes: dict = None, should_error: bool = False):
"""Configure a specific span."""
self.spans_config[name] = {
"attributes": attributes or {},
"should_error": should_error
}
return self
def with_global_config(self, **kwargs):
"""Configure global tracer behavior."""
self.global_config.update(kwargs)
return self
def build(self):
"""Build the configured mock tracer."""
def mock_trace(name, **kwargs):
if name in self.spans_config:
config = self.spans_config[name]
if config["should_error"]:
raise Exception(f"Configured error for {name}")
span = MockSpan(name)
for key, value in config["attributes"].items():
span.set_attribute(key, value)
return span
return MockSpan(name)
self.mock_tracer.trace = mock_trace
# Configure global properties
for key, value in self.global_config.items():
setattr(self.mock_tracer, key, value)
return self.mock_tracer
def test_builder_pattern():
"""Test mock tracer builder pattern."""
mock_tracer = (MockTracerBuilder()
.with_span("db-query", {"db.table": "users"})
.with_span("api-call", {"http.status": 200})
.with_span("error-operation", should_error=True)
.with_global_config(api_key="test-key")
.build())
# Test configured spans
db_span = mock_tracer.trace("db-query")
assert db_span.get_attribute("db.table") == "users"
api_span = mock_tracer.trace("api-call")
assert api_span.get_attribute("http.status") == 200
# Test error span
with pytest.raises(Exception, match="Configured error"):
mock_tracer.trace("error-operation")
# Test global config
assert mock_tracer.api_key == "test-key"
assert mock_tracer.project == "test"
Mock Validation Utilities
Problem: Create utilities to validate mock interactions.
Solution - Validation Framework:
"""Utilities for validating mock interactions."""
from typing import List, Dict, Any, Optional
import re
class MockValidator:
"""Utilities for validating mock tracer interactions."""
def __init__(self, mock_tracer):
self.mock_tracer = mock_tracer
def assert_span_count(self, expected_count: int):
"""Assert expected number of spans were created."""
actual_count = len(self.mock_tracer.spans)
assert actual_count == expected_count, f"Expected {expected_count} spans, got {actual_count}"
def assert_span_names(self, expected_names: List[str]):
"""Assert specific span names were created."""
actual_names = [span.name for span in self.mock_tracer.spans]
assert actual_names == expected_names, f"Expected {expected_names}, got {actual_names}"
def assert_span_attributes(self, span_name: str, expected_attributes: Dict[str, Any]):
"""Assert span has expected attributes."""
span = self.mock_tracer.get_span_by_name(span_name)
assert span is not None, f"Span '{span_name}' not found"
for key, expected_value in expected_attributes.items():
actual_value = span.get_attribute(key)
assert actual_value == expected_value, f"Span '{span_name}' attribute '{key}': expected {expected_value}, got {actual_value}"
def assert_span_pattern(self, pattern: str):
"""Assert span names match a pattern."""
regex = re.compile(pattern)
for span in self.mock_tracer.spans:
assert regex.match(span.name), f"Span name '{span.name}' doesn't match pattern '{pattern}'"
def assert_flush_called(self, times: int = None):
"""Assert force_flush was called."""
flush_calls = len(self.mock_tracer.flush_calls)
if times is not None:
assert flush_calls == times, f"Expected {times} flush calls, got {flush_calls}"
else:
assert flush_calls > 0, "Expected at least one flush call"
def assert_no_errors(self):
"""Assert no spans recorded errors."""
for span in self.mock_tracer.spans:
assert span.status != "ERROR", f"Span '{span.name}' has error status"
assert not span.exceptions, f"Span '{span.name}' recorded exceptions: {span.exceptions}"
def assert_span_hierarchy(self, expected_hierarchy: Dict[str, List[str]]):
"""Assert span parent-child relationships."""
# This would need more sophisticated implementation
# based on how span hierarchy is tracked in your mock
pass
def get_interaction_summary(self) -> Dict[str, Any]:
"""Get summary of all mock interactions."""
return {
"total_spans": len(self.mock_tracer.spans),
"span_names": [span.name for span in self.mock_tracer.spans],
"total_attributes": sum(len(span.attributes) for span in self.mock_tracer.spans),
"total_events": sum(len(span.events) for span in self.mock_tracer.spans),
"error_spans": [span.name for span in self.mock_tracer.spans if span.status == "ERROR"],
"flush_calls": len(self.mock_tracer.flush_calls),
"close_calls": len(self.mock_tracer.close_calls)
}
def test_with_validation():
"""Example of using mock validation utilities."""
mock_tracer = MockHoneyHiveTracer()
validator = MockValidator(mock_tracer)
# Run code under test
with mock_tracer.trace("operation-1") as span:
span.set_attribute("step", 1)
with mock_tracer.trace("operation-2") as span:
span.set_attribute("step", 2)
mock_tracer.force_flush()
# Validate interactions
validator.assert_span_count(2)
validator.assert_span_names(["operation-1", "operation-2"])
validator.assert_span_attributes("operation-1", {"step": 1})
validator.assert_span_attributes("operation-2", {"step": 2})
validator.assert_flush_called(times=1)
validator.assert_no_errors()
# Get summary
summary = validator.get_interaction_summary()
print(f"Test summary: {summary}")
Best Practices for Mocking
Mocking Guidelines:
Mock at the Right Level: Mock at the boundary of your code, not deep internals
Use Realistic Mocks: Make mocks behave like the real system
Verify Interactions: Check that your code calls mocks as expected
Test Error Scenarios: Mock failures to test error handling
Keep Mocks Simple: Don’t make mocks more complex than necessary
Reset Between Tests: Ensure mocks are clean for each test
Document Mock Behavior: Make it clear what the mock represents
Common Patterns:
# Pattern 1: Mock with side effects
mock_tracer.trace.side_effect = [
MockSpan("span1"),
MockSpan("span2"),
Exception("Third call fails")
]
# Pattern 2: Mock with return values based on arguments
def trace_side_effect(name, **kwargs):
if "error" in name:
raise Exception(f"Error in {name}")
return MockSpan(name)
mock_tracer.trace.side_effect = trace_side_effect
# Pattern 3: Partial mocking
real_tracer = HoneyHiveTracer.init(api_key="test", test_mode=True)
real_tracer.trace = Mock(side_effect=real_tracer.trace)
# Pattern 4: Property mocking
with patch.object(HoneyHiveTracer, 'session_id', new_callable=PropertyMock) as mock_session_id:
mock_session_id.return_value = "mock-session-123"
See Also
Unit Testing Strategies - Unit testing strategies using mocks
Integration Testing Strategies - When to use mocks vs real integrations
Troubleshooting Test Issues - Debugging issues with mocks
HoneyHiveTracer API Reference - Real tracer API for accurate mocking