Testing
Test code that uses llm-minder without spending money on API calls.
Provider-free FakeStrategy
from llm_minder import process_prompts
from llm_minder.testing import FakeStrategy
batch = await process_prompts(
FakeStrategy(lambda prompt: prompt.upper(), token_usage={"input_tokens": 2, "output_tokens": 1}),
["hello", "world"],
preserve_order=True, # results otherwise arrive in completion order
)
assert list(batch.outputs()) == ["HELLO", "WORLD"]
FakeStrategy accepts a fixed response or synchronous response(prompt) function,
latency in seconds, and canonical or OpenAI-style token counters. It needs no extras.
failure_schedule=[FakeRateLimitError(), None] makes the first physical call raise
a simulated 429 and the next succeed; import FakeRateLimitError from
llm_minder.testing. Each entry corresponds to invocation order, including retries.
After the schedule ends, calls succeed unless failure_rate is set. A nonzero
failure_rate requires seed= for reproducibility. Concurrent scheduling can change
which item receives a scheduled failure. Pass identity=ArtifactIdentity(...) for
artifact persistence. mock_strategy remains available with its existing behavior.
1. Dry-run mode (no API calls)
ProcessorConfig(dry_run=True) runs the whole pipeline — workers, retries,
post-processors — but calls each strategy's dry_run() instead of execute(),
returning mock output. Good for validating configuration and wiring end to end.
config = ProcessorConfig(dry_run=True) # no API calls made
async with ParallelBatchProcessor(config=config) as processor:
await processor.add_work(work_item)
result = await processor.process_all() # returns mock data
2. Mock strategies (unit tests)
MockAgent simulates an LLM — latency, rate limits, errors, token usage — with
no network, so it's far faster than real integration tests.
from llm_minder.testing import MockAgent
mock_agent = MockAgent(
response_factory=lambda p: Summary(title="Test", key_points=["A", "B"]),
latency=0.01, # simulate 10ms latency
rate_limit_on_call=3, # simulate a 429 on the 3rd call (once)
failure_rate=0.0, # probability of a random failure per call
)
strategy = PydanticAIStrategy(agent=mock_agent)
For a custom strategy, write a tiny LLMCallStrategy subclass whose execute()
returns canned (output, tokens, metadata); the older (output, tokens) 2-tuple is
deprecated and removed in 1.0. See the framework's own
tests/ for many examples (rate-limit exemption, token accounting, streaming,
backpressure, etc.).
3. Small-batch integration tests
Before a 1,000-item run, validate the real pipeline on a handful of items:
test_items = full_dataset[:5]
config = ProcessorConfig(concurrency=2, attempt_timeout=30.0)
result = await process_prompts(strategy, test_items, config=config)
assert result.succeeded == len(test_items)
# ...then process the full batch
Real API calls in the framework's own suite live behind the integration
pytest marker and are skipped by default.
4. Catching deprecations early
After 1.0, a name or behavior is deprecated for at least one minor release before a
major release removes it. Each deprecation emits a DeprecationWarning, which Python
hides outside __main__, so turn them into errors in your test run:
Other packages can emit their own DeprecationWarnings (google-genai does at import
on Python 3.14); add an ignore filter for those modules, for example
-W "ignore::DeprecationWarning:google.genai.types". Upgrading from 0.x? The
removed-in-1.0 table lists every name 1.0 removed and
its replacement.