kgb
Function spies for Python unit tests that intercept, record, and reroute calls without heavy-handed mocking.
Repository Health
Technical Analysis
kgb gives Python test suites a spy-based alternative to traditional mocking. Instead of replacing an entire object or module, a spy wraps a specific function or method in place, recording every call it receives — arguments, return values, and raised exceptions — while optionally letting the original implementation run, blocking it entirely, or rerouting it to a fake implementation supplied by the test.
The library is built around a SpyAgency, which can be instantiated standalone, mixed into a unittest.TestCase, or accessed through a bundled pytest fixture. A rich set of assertSpyCalled/assert_spy_called-style assertions (and their pytest-fixture and standalone-function equivalents) let tests check not just whether a function was called, but with what arguments, what it returned, and whether it raised. Ordered-call operations (SpyOpMatchInOrder, SpyOpReturnInOrder, SpyOpRaiseInOrder) support scripting a sequence of expected calls and responses for more complex interaction tests.
What You Get
- A
SpyAgencyclass that tracks every spy it creates and can remove them individually or all at once, usable standalone, as aunittest.TestCasemixin, or via a pytest fixture - Four ways to start spying: an agency instance, a
TestCasemixin, the@spy_fordecorator, or thespy_oncontext manager - Call recording on every spied function, including
.calls,.last_call, and per-call.args/.kwargs/.return_value/.exception - Rich assertions (
assertSpyCalled,assertSpyCalledWith,assertSpyReturned,assertSpyRaised, and more) available asTestCasemethods,spy_agencyfixture methods, or standalone functions fromkgb.asserts - Ordered-call operations (
SpyOpMatchInOrder,SpyOpReturnInOrder,SpyOpRaiseInOrder,SpyOpMatchAny) for scripting multi-call interaction sequences - A first-class
pytest11plugin entry point that registers aspy_agencyfixture with automatic teardown viaunspy_all()
Common Use Cases
- Verifying a method was called with specific arguments without mocking out the entire object it belongs to
- Faking the return value or side effect of an expensive or external call (network, filesystem, subprocess) inside a unit test
- Asserting on the exact sequence and arguments of several calls to the same function across a test
- Blocking a dangerous or side-effecting function entirely while still observing that code attempted to call it
- Adding lightweight instrumentation to non-test code paths to observe function usage during debugging
Under The Hood
Architecture
kgb centers on kgb/agency.py’s SpyAgency, a registry/facade over kgb/spies.py’s FunctionSpy — the core class that performs the actual function patching, using kgb/signature.py to introspect and validate call signatures, and kgb/calls.py to record each invocation as a SpyCall object. Operations in kgb/ops.py implement a strategy pattern — BaseSpyOperation subclasses such as SpyOpReturnInOrder or SpyOpRaiseInOrder plug into a spy’s call handling to script multi-call behavior without the caller writing custom fake functions. kgb/contextmanagers.py and kgb/pytest_plugin.py are thin adapters wrapping the agency/spy primitives into a context manager and a pytest fixture entry point, so nearly all core logic lives in agency.py and spies.py while the rest of the package is integration surface.
Tech Stack
Pure Python with zero runtime dependencies, targeting Python 2.7 and 3.6 through 3.13 plus PyPy, per pyproject.toml’s requires-python and tox.ini’s extensive environment matrix. Packaging uses setuptools with a dynamic version sourced from kgb/__init__.py’s get_package_version(), and the package registers itself with pytest via the pytest11 entry point. Testing runs on pytest, orchestrated across the full version matrix by tox, with only pytest (and unittest2 for legacy Python 2.7 support) as dev dependencies. There’s no web framework, database, or service layer here — this is a leaf testing utility meant to be imported, not run.
Code Quality
kgb ships an extensive test suite — test_function_spy.py alone contains well over a hundred test functions, with test_spy_agency.py and test_ops.py each running to hundreds of lines — executed via pytest and tox across nine-plus Python/PyPy targets. Every public class and method carries a full docstring with Args/Returns/Raises and Version Added/Changed sections, effectively serving as an in-code changelog. Error handling is explicit and typed through custom exception classes (ExistingSpyError, IncompatibleFunctionError, InternalKGBError, UnexpectedCallError) rather than generic exceptions, and ExistingSpyError goes further by capturing and replaying the original spy’s stack trace to aid debugging. No type annotations are used, a consequence of maintaining Python 2.7 compatibility, and no CI workflow configuration is present in the repository, so verification across the version matrix appears to be run manually via tox rather than on every push.
What Makes It Unique
kgb’s core idea — spying that defaults to pass-through-and-record rather than mocking’s replace-and-stub — is a genuine, if narrow, ergonomic refinement: a spy can observe real behavior without requiring the caller to also define what it returns, layering on call_fake or call_original=False only when needed. Four equivalent ways to start spying (agency instance, TestCase mixin, decorator, context manager) and three parallel assertion styles (TestCase methods, pytest fixture methods, standalone functions) let the same functionality reach unittest-based, pytest-based, and mixed codebases without forcing a migration. This is a well-executed adaptation of the Jasmine-style spy pattern — explicitly credited in the README — to Python, rather than a wholly novel testing paradigm.