At Most Once Fire-and-Forget Mock in Python
A Python mock that enforces send() is called at most once and records the arguments for verification.
Python code
27 linesclass FireForgetMock:
def __init__(self):
self._calls = 0
self._last_args = None
self._last_kwargs = None
def send(self, *args, **kwargs):
if self._calls > 0:
raise RuntimeError("send() called more than once")
self._calls += 1
self._last_args = args
self._last_kwargs = kwargs
def assert_called_once(self):
if self._calls != 1:
raise AssertionError(f"Expected exactly 1 call, got {self._calls}")
def get_call_details(self):
return self._last_args, self._last_kwargs
if __name__ == "__main__":
mock = FireForgetMock()
mock.send("notification-123", priority="high")
mock.assert_called_once()
args, kwargs = mock.get_call_details()
print(f"Args: {args}, Kwargs: {kwargs}")
Output
Args: ('notification-123',), Kwargs: {'priority': 'high'}
How it works
The mock uses a counter _calls to track invocations. On the first call, it stores the arguments and increments the counter; any subsequent call raises a RuntimeError, enforcing at-most-once semantics. The assert_called_once method checks that exactly one call happened, failing with a descriptive assertion otherwise. The recorded arguments are retrievable via get_call_details, making it easy to verify what was sent in tests.
Common mistakes
- Forgetting to reset the mock between test cases, causing state leakage
- Checking `args` or `kwargs` length without handling the case where they might be empty
- Raising `AssertionError` without including the call count, making failures hard to debug
Variations
- Use `unittest.mock.Mock` with `side_effect` to raise on multiple calls
- Use a delegate function that appends to a list for more complex call history
Real-world use cases
- Stubbing a message queue producer to ensure an event is published only once per request.
- Verifying that a webhook notifier sends exactly one notification in a rate-limited service.
- Testing an idempotent job that must trigger a downstream API call at most once.
Sponsored
More from Streaming & messaging
- Batch Consume Process Commit Pattern in Python medium
- Build a Streaming Messaging Helper in Python easy
- Dead Letter Queue Failed Messages List Mock in Python easy
- Dedupe processed message IDs in Python easy
- Event Envelope with Schema Version Field in Python easy
- Event sourcing append store replay in Python easy
Keep learning
Related tutorials and quizzes for this topic.