Testing Module¶
testing
¶
Public test helpers for functualize job testing.
Provides test doubles and builders for unit testing jobs:
from functualize.testing import TestRunContext, CapturingLog, MockInvoke, AutoPrompt, NoopPerf
from functualize.testing import FakeShell, FakeStdout
__all__ = ['AutoPrompt', 'CapturingLog', 'FakeShell', 'FakeShellCall', 'FakeStdout', 'MockInvoke', 'NoopPerf', 'TestRunContext']
module-attribute
¶
TestRunContext
¶
Builder for test RunContext instances with sensible defaults.
Example::
from functualize.testing import TestRunContext
rc = TestRunContext.create()
# Use rc in your job function under test
# Or with overrides:
rc = TestRunContext.create(log=my_custom_log, state=my_state)
rc.log(...) emits through the same Log the job would receive as an
injected parameter, so messages logged either way are recorded by the
default CapturingLog and can be asserted through captured_logs().
create(*, log=None, invoke=None, prompt=None, perf=None, state=None, job_context=None)
staticmethod
¶
Create a RunContext for testing with optional capability overrides.
Each omitted parameter uses a default test double: - log: CapturingLog (records all calls) - invoke: MockInvoke({}) (no configured results) - prompt: AutoPrompt([]) (no pre-configured answers) - perf: NoopPerf (silently accepts all calls) - state: empty State instance - job_context: JobContext(name="test", trace_id=None, metadata=empty)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log
|
Log | None
|
Override for the Log capability. |
None
|
invoke
|
Invoke | None
|
Override for the Invoke capability. |
None
|
prompt
|
Prompt | None
|
Override for the Prompt capability. |
None
|
perf
|
Perf | None
|
Override for the Perf capability. |
None
|
state
|
State | None
|
Override for the State capability. |
None
|
job_context
|
JobContext | None
|
Override for the JobContext capability. |
None
|
Returns:
| Type | Description |
|---|---|
RunContext
|
A fully-constructed RunContext backed by a DIRegistry containing |
RunContext
|
the resolved test doubles. |
Source code in src/functualize/testing/builder.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | |
captured_logs(rc)
staticmethod
¶
Retrieve the ordered list of (level, message) tuples from the RunContext's log.
Accesses the CapturingLog instance registered in the RunContext's DI registry
and returns its recorded calls. Messages emitted via rc.log(...) are
included: the same double sits in the RunContext's per-invocation
capability map, which is where RunContext.log() takes its sink from.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rc
|
RunContext
|
A RunContext created by TestRunContext.create(). |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[str, object]]
|
Ordered list of (level, message) tuples recorded by the CapturingLog. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the RunContext has no DI registry attached. |
TypeError
|
If the registered Log is not a CapturingLog instance. |
Source code in src/functualize/testing/builder.py
AutoPrompt(responses=None)
¶
Bases: Prompt
Test double for the Prompt capability with FIFO responses.
Accepts a sequence of responses at construction and returns them one at a time in order. Raises IndexError when exhausted.
Example
prompt = AutoPrompt(["yes", True, "choice_a"]) prompt.text("Name?") # returns "yes" prompt.confirm("Sure?") # returns True prompt.text("Pick one?") # returns "choice_a" prompt.text("Another?") # raises IndexError
Source code in src/functualize/testing/doubles.py
ask(request)
¶
Return the next pre-configured response.
Raises:
| Type | Description |
|---|---|
IndexError
|
When exhausted. |
confirm(question, *, default=None, **kwargs)
¶
Return the next pre-configured response.
Raises:
| Type | Description |
|---|---|
IndexError
|
When exhausted. |
choice(question, choices, **kwargs)
¶
Return the next pre-configured response.
Raises:
| Type | Description |
|---|---|
IndexError
|
When exhausted. |
text(question, *, default=None, **kwargs)
¶
Return the next pre-configured response.
Raises:
| Type | Description |
|---|---|
IndexError
|
When exhausted. |
CapturingLog()
¶
Bases: Log
Test double for the Log capability that records all log calls.
Each call is stored as a (level, message) tuple in insertion order. Supports both the call syntax and named level methods.
Example
log = CapturingLog() log("hello") log.warning("watch out") assert log.calls == [("info", "hello"), ("warning", "watch out")]
Source code in src/functualize/testing/doubles.py
calls = []
instance-attribute
¶
__call__(message, level='info')
¶
Record a log call as (level, message).
Validates the level exactly as the real Log does, so a bad level fails in tests instead of being recorded and only failing in production.
Raises:
| Type | Description |
|---|---|
ValueError
|
If level is not a valid log level. |
Source code in src/functualize/testing/doubles.py
info(msg)
¶
warning(msg)
¶
error(msg)
¶
MockInvoke(results=None)
¶
Bases: Invoke
Test double for the Invoke capability with pre-configured results.
Accepts a mapping of job names to result values at construction. Returns the mapped result when invoked; raises KeyError on unknown job names.
Example
invoke = MockInvoke({"deploy": result_obj}) result = invoke("deploy") # returns result_obj invoke("unknown") # raises KeyError
Source code in src/functualize/testing/doubles.py
__call__(job_or_fn, **kwargs)
¶
Return the pre-configured result for the given job.
Every keyword the real Invoke accepts is absorbed and ignored, so a job
that passes config=, timeout=, or a gate option behaves the same
under test as it does in production.
Raises:
| Type | Description |
|---|---|
KeyError
|
If the job has no configured result. |
Source code in src/functualize/testing/doubles.py
parallel(jobs, **kwargs)
¶
Return pre-configured results for each job in order.
Raises:
| Type | Description |
|---|---|
KeyError
|
If any job has no configured result. |
Source code in src/functualize/testing/doubles.py
schema(job_or_fn)
¶
Return the pre-configured result for the given job as schema.
For testing purposes, this returns whatever is mapped for the job name.
Raises:
| Type | Description |
|---|---|
KeyError
|
If the job has no configured result. |
Source code in src/functualize/testing/doubles.py
NoopPerf
¶
Bases: Perf
Test double for the Perf capability that silently accepts all calls.
All mark, mark_start, and mark_end calls are accepted without recording or raising. phases() returns an empty list.
Example
perf = NoopPerf() perf.mark("init") # no-op perf.mark_start("phase_1") # no-op perf.mark_end("phase_1") # no-op perf.phases() # returns []
FakeShell(mapping=None)
¶
A scripted, recording stand-in for the Shell capability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping
|
dict[Any, ShellResult] | None
|
Maps an exact command string or a compiled regex to the
|
None
|
Source code in src/functualize/testing/shell.py
calls = []
instance-attribute
¶
deferred
property
¶
The cleanup commands queued so far, in registration order.
__call__(command, *, check=True, **kwargs)
¶
Resolve, record, and answer a command from the mapping.
Raises:
| Type | Description |
|---|---|
AssertionError
|
If no mapping entry matches (loud on unexpected). |
Source code in src/functualize/testing/shell.py
sudo(command, **kwargs)
¶
Record a sudo-prefixed call and answer it from the mapping.
Mirrors :meth:WiredShell.sudo for testability: the recorded command is
sudo <command> (no -S/password machinery — a fake never spawns a
real sudo). preserve_env/password/watchers are accepted and
ignored. Match sudo <command> in the mapping like any other command.
Source code in src/functualize/testing/shell.py
cd(path)
¶
Record commands in the block as running in path (§B.3).
Nestable, resolving relative to the enclosing one, exactly as
WiredShell.cd does. The effective directory lands on each recorded
call's kwargs["cwd"], so a test can assert where a command ran
without the fake spawning anything.
Source code in src/functualize/testing/shell.py
prefix(command)
¶
Prepend command to every command in the block (§B.3).
The prefix is applied before matching, because the real shell runs
the prefixed argv — so a mapping keyed on poetry run pytest is what
matches inside with sh.prefix(["poetry", "run"]). Folding it in
afterwards would let a test pass against a command that never ran.
Source code in src/functualize/testing/shell.py
defer(command, **kwargs)
¶
Queue a cleanup command, as WiredShell.defer does (§B.5).
Nothing runs until :meth:run_deferred. In a real run the engine calls
that on the job-exit unwind; in a test, call it yourself — or assert on
:attr:deferred to check what a job registered without running it.
Source code in src/functualize/testing/shell.py
run_deferred()
¶
Run and clear the queued cleanups, LIFO.
check=False by default, like the real unwind: a cleanup that fails
must not mask the job's own outcome. Unlike the real unwind, an
unexpected command still raises — a fake that silently swallowed an
unmapped cleanup would be a fake that cannot be asserted on.
Source code in src/functualize/testing/shell.py
FakeShellCall(argv, command, kwargs=dict())
dataclass
¶
FakeStdout(output_format='auto')
¶
In-memory Stdout double.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_format
|
str
|
The format |
'auto'
|
Source code in src/functualize/testing/stdout.py
emitted = []
instance-attribute
¶
writes = []
instance-attribute
¶
text
property
¶
Everything written so far, as a pipe consumer would see it.
emit(value)
¶
Record value and render it per the configured format.
Source code in src/functualize/testing/stdout.py
write(data)
¶
Record and buffer a raw passthrough write.
Source code in src/functualize/testing/stdout.py
Overview¶
functualize.testing provides test doubles and builders for unit testing jobs without spawning processes, hitting the filesystem, or standing up a live app.
Module location: src/functualize/testing/
Public API¶
from functualize.testing import (
TestRunContext,
CapturingLog,
MockInvoke,
AutoPrompt,
NoopPerf,
FakeShell,
FakeShellCall,
FakeStdout,
)
TestRunContext, CapturingLog, MockInvoke, AutoPrompt, NoopPerf, and FakeShell are covered in the Composing Capabilities and Shell Capability guides. FakeShellCall and FakeStdout are documented here.
FakeShellCall¶
A single recorded FakeShell invocation. FakeShell (the Shell capability's test double) appends one of these to fake.calls for every command it resolves, whether or not the command was actually mapped to a result.
@dataclass(frozen=True)
class FakeShellCall:
argv: list[str]
command: str
kwargs: dict[str, Any] = field(default_factory=dict)
| Attribute | Type | Description |
|---|---|---|
argv |
list[str] |
The resolved argument vector — the list form, or the display string split back apart. |
command |
str |
The display string the command resolved to (what a mapping key matches against). |
kwargs |
dict[str, Any] |
The keyword options passed to the call — includes an effective cwd when the call ran inside a sh.cd(...) block. |
from functualize.testing import FakeShell
fake = FakeShell({"git status": ShellResult(0, "clean\n", "", "git status", 12.0, None)})
deploy(sh=fake)
assert fake.calls[0].argv == ["git", "status"]
assert fake.calls[0].command == "git status"
FakeStdout¶
An in-memory double for the Stdout capability (out: Stdout). Records what a job emits so pipeline behavior can be asserted without capturing a real process's stdout.
class FakeStdout:
def __init__(self, output_format: str = "auto") -> None: ...
emitted: list[Any] # objects passed to emit(), in order
writes: list[str | bytes] # raw payloads passed to write(), in order
@property
def text(self) -> str: ... # everything written so far, as a pipe consumer would see it
def emit(self, value: Any) -> None: ...
def write(self, data: str | bytes) -> None: ...
| Member | Type | Description |
|---|---|---|
output_format (constructor arg) |
str |
The format emit renders with — "auto" (default, dispatch by value type), "json", "ndjson", "raw", or "none". Mirrors the --emit-format flag so a test can pin the wire shape a caller would get. |
emitted |
list[Any] |
The objects handed to emit(), in order — assert on data, not on formatting. |
writes |
list[str \| bytes] |
Raw write() payloads, in order. |
text (property) |
str |
The rendered stream exactly as a pipe would see it — assert on the wire format. |