HTTP traffic mocking and expectations made easy for Python testing and development
—
State inspection and debugging functions for examining mock states, pending mocks, unmatched requests, and engine status. These functions are essential for debugging test failures, verifying mock behavior, and understanding pook's internal state during development and testing.
Functions for checking the status and completion state of registered mocks.
def pending():
"""
Returns the number of pending mocks to be matched.
Pending mocks are mocks that have been registered but not yet
matched by any HTTP requests.
Returns:
int: Number of pending mocks
"""
def ispending():
"""
Returns the number of pending mocks to be matched (alias to pending()).
Returns:
int: Number of pending mocks
"""
def pending_mocks():
"""
Returns pending mocks to be matched.
Returns the actual Mock instances that are still waiting to be
matched by HTTP requests.
Returns:
list: List of pending Mock instances
"""
def isdone():
"""
Returns True if all registered mocks have been triggered.
A mock engine is considered "done" when all registered mocks
have been matched at least once (or up to their times() limit).
Returns:
bool: True if all mocks have been consumed, False otherwise
"""Usage examples:
import pook
import requests
# Setup multiple mocks
pook.activate()
pook.get('https://api.example.com/users').reply(200).json([])
pook.get('https://api.example.com/posts').reply(200).json([])
pook.post('https://api.example.com/users').reply(201)
print(f"Pending mocks: {pook.pending()}") # 3
print(f"Is pending: {pook.ispending()}") # 3 (alias)
print(f"All done: {pook.isdone()}") # False
# Make some requests
requests.get('https://api.example.com/users') # Matches first mock
print(f"Pending mocks: {pook.pending()}") # 2
print(f"All done: {pook.isdone()}") # False
requests.get('https://api.example.com/posts') # Matches second mock
print(f"Pending mocks: {pook.pending()}") # 1
requests.post('https://api.example.com/users') # Matches third mock
print(f"Pending mocks: {pook.pending()}") # 0
print(f"All done: {pook.isdone()}") # True
# Inspect pending mock details
pook.get('https://api.example.com/new').reply(200)
pending_list = pook.pending_mocks()
print(f"Pending mock URLs: {[mock.request.rawurl for mock in pending_list]}")
pook.off()Functions for examining requests that didn't match any registered mocks, primarily useful in networking mode for debugging.
def unmatched_requests():
"""
Returns a list of unmatched requests (only available in networking mode).
When networking mode is enabled, pook tracks requests that didn't
match any registered mocks. This is useful for debugging why
certain requests weren't intercepted.
Returns:
list: List of unmatched intercepted Request objects
"""
def unmatched():
"""
Returns the total number of unmatched requests intercepted by pook.
Returns:
int: Total number of unmatched requests
"""
def isunmatched():
"""
Returns True if there are unmatched requests, otherwise False.
Returns:
bool: True if any requests were intercepted but not matched
"""Usage examples:
import pook
import requests
# Enable networking to track unmatched requests
pook.activate()
pook.enable_network()
# Register some mocks
pook.get('https://api.example.com/users').reply(200).json([])
pook.post('https://api.example.com/users').reply(201)
# Make requests - some match, some don't
requests.get('https://api.example.com/users') # Matches mock
requests.get('https://api.example.com/posts') # No mock, goes to network
requests.delete('https://api.example.com/users/1') # No mock, goes to network
print(f"Unmatched count: {pook.unmatched()}") # 2
print(f"Has unmatched: {pook.isunmatched()}") # True
# Inspect unmatched requests
unmatched_reqs = pook.unmatched_requests()
for req in unmatched_reqs:
print(f"Unmatched: {req.method} {req.rawurl}")
# Output:
# Unmatched: GET https://api.example.com/posts
# Unmatched: DELETE https://api.example.com/users/1
pook.off()Functions for checking the current state and activity status of pook's engine.
def isactive():
"""
Returns True if pook is active and intercepting traffic, otherwise False.
The engine is active when activate() has been called and
before disable() or off() is called.
Returns:
bool: Current engine activation status
"""Usage examples:
import pook
print(f"Initially active: {pook.isactive()}") # False
pook.activate()
print(f"After activate: {pook.isactive()}") # True
pook.disable()
print(f"After disable: {pook.isactive()}") # False
pook.activate()
print(f"Reactivated: {pook.isactive()}") # True
pook.off() # Disable and reset
print(f"After off: {pook.isactive()}") # FalsePractical patterns for using state inspection functions to debug complex testing scenarios.
import pook
import requests
def test_all_mocks_consumed():
"""Ensure all expected API calls were made."""
pook.activate()
# Setup expected API calls
pook.get('https://api.example.com/config').reply(200).json({'version': '1.0'})
pook.get('https://api.example.com/users').reply(200).json([])
pook.post('https://api.example.com/analytics').reply(201)
# Run application code that should make these calls
# ... application code here ...
# Verify all mocks were consumed
if not pook.isdone():
pending = pook.pending_mocks()
urls = [mock.request.rawurl for mock in pending]
raise AssertionError(f"Unconsumed mocks: {urls}")
pook.off()
def test_no_unexpected_requests():
"""Ensure no unexpected network requests were made."""
pook.activate()
pook.enable_network()
# Setup expected mocks
pook.get('https://api.example.com/expected').reply(200)
# Run application code
# ... application code here ...
# Check for unexpected requests
if pook.isunmatched():
unmatched = pook.unmatched_requests()
unexpected = [f"{req.method} {req.rawurl}" for req in unmatched]
raise AssertionError(f"Unexpected requests: {unexpected}")
pook.off()import pook
import requests
class MockMonitor:
"""Helper class for monitoring mock state during tests."""
def __init__(self):
self.snapshots = []
def snapshot(self, label):
"""Take a snapshot of current mock state."""
snapshot = {
'label': label,
'active': pook.isactive(),
'pending': pook.pending(),
'unmatched': pook.unmatched(),
'done': pook.isdone()
}
self.snapshots.append(snapshot)
return snapshot
def report(self):
"""Generate a report of all snapshots."""
for snap in self.snapshots:
print(f"{snap['label']}: active={snap['active']}, "
f"pending={snap['pending']}, unmatched={snap['unmatched']}, "
f"done={snap['done']}")
# Usage
monitor = MockMonitor()
pook.activate()
pook.enable_network()
monitor.snapshot("After activation")
# Setup mocks
pook.get('https://api.example.com/step1').reply(200).json({'step': 1})
pook.get('https://api.example.com/step2').reply(200).json({'step': 2})
monitor.snapshot("After mock setup")
# Execute step 1
requests.get('https://api.example.com/step1')
monitor.snapshot("After step 1")
# Execute step 2
requests.get('https://api.example.com/step2')
monitor.snapshot("After step 2")
# Make unexpected request
requests.get('https://api.example.com/unexpected')
monitor.snapshot("After unexpected request")
monitor.report()
pook.off()
# Output:
# After activation: active=True, pending=0, unmatched=0, done=True
# After mock setup: active=True, pending=2, unmatched=0, done=False
# After step 1: active=True, pending=1, unmatched=0, done=False
# After step 2: active=True, pending=0, unmatched=0, done=True
# After unexpected request: active=True, pending=0, unmatched=1, done=Trueimport pook
import requests
def inspect_mock_details():
"""Detailed inspection of individual mock states."""
pook.activate()
# Create mocks with different configurations
mock1 = pook.get('https://api.example.com/limited').times(2).reply(200)
mock2 = pook.get('https://api.example.com/persistent').persist().reply(200)
mock3 = pook.get('https://api.example.com/unused').reply(200)
def print_mock_state(mock, name):
print(f"{name}:")
print(f" Done: {mock.isdone()}")
print(f" Matched: {mock.ismatched()}")
print(f" Total matches: {mock.total_matches}")
print(f" Calls: {mock.calls}")
print("Initial state:")
print_mock_state(mock1, "Limited mock (2 times)")
print_mock_state(mock2, "Persistent mock")
print_mock_state(mock3, "Unused mock")
# Make some requests
requests.get('https://api.example.com/limited') # First hit
requests.get('https://api.example.com/persistent') # First hit
print("\nAfter first requests:")
print_mock_state(mock1, "Limited mock")
print_mock_state(mock2, "Persistent mock")
print_mock_state(mock3, "Unused mock")
requests.get('https://api.example.com/limited') # Second hit (expires)
requests.get('https://api.example.com/persistent') # Second hit (continues)
print("\nAfter second requests:")
print_mock_state(mock1, "Limited mock")
print_mock_state(mock2, "Persistent mock")
print_mock_state(mock3, "Unused mock")
print(f"\nOverall state:")
print(f" Total pending: {pook.pending()}")
print(f" All done: {pook.isdone()}")
# List remaining pending mocks
pending = pook.pending_mocks()
if pending:
print(f" Pending mock URLs: {[m.request.rawurl for m in pending]}")
pook.off()import pook
import requests
def debug_mock_matching():
"""Helper for debugging why requests aren't matching mocks."""
pook.activate()
# Setup a mock with specific criteria
mock = pook.post('https://api.example.com/users') \
.header('Content-Type', 'application/json') \
.json({'name': 'John', 'age': 30}) \
.reply(201)
print(f"Mock registered. Pending: {pook.pending()}")
# Make a request that might not match
try:
response = requests.post('https://api.example.com/users',
json={'name': 'John', 'age': '30'}, # age as string
headers={'Content-Type': 'application/json'})
print(f"Request succeeded: {response.status_code}")
except Exception as e:
print(f"Request failed: {e}")
# Check what happened
print(f"After request - Pending: {pook.pending()}")
print(f"Mock matched: {mock.ismatched()}")
if not mock.ismatched():
print("Mock was not matched. Possible issues:")
print("- Request body didn't match expected JSON structure")
print("- Headers didn't match")
print("- URL didn't match")
print("- HTTP method didn't match")
pook.off()
def validate_test_cleanup():
"""Ensure proper test cleanup and isolation."""
def run_test():
pook.activate()
pook.get('https://api.example.com/test').reply(200)
# Simulate test that forgets cleanup
# pook.off() # Commented out - simulates forgotten cleanup
# Before test
print(f"Before test - Active: {pook.isactive()}, Pending: {pook.pending()}")
run_test()
# After test
print(f"After test - Active: {pook.isactive()}, Pending: {pook.pending()}")
if pook.isactive() or pook.pending() > 0:
print("WARNING: Test didn't clean up properly!")
print("This could affect other tests.")
pook.off() # Force cleanup
print(f"After cleanup - Active: {pook.isactive()}, Pending: {pook.pending()}")class MatcherEngine:
"""
HTTP request matcher engine used by Mock to test if an intercepted
outgoing HTTP request should be mocked out.
"""
def match(self, request):
"""
Matches HTTP request against registered matchers.
Parameters:
- request: HTTP request to match
Returns:
tuple: (bool, list[str]) - Match success and error messages
"""Usage examples:
import pook
# Access matcher engine for detailed debugging
mock = pook.get('https://api.example.com/test')
mock.json({'required': 'field'}).reply(200)
# Simulate a request for testing
from pook import Request
test_request = Request(
method='GET',
url='https://api.example.com/test',
json={'different': 'field'}
)
# Test if request would match
success, errors = mock._matchers.match(test_request)
print(f"Would match: {success}")
if errors:
print(f"Match errors: {errors}")Install with Tessl CLI
npx tessl i tessl/pypi-pook