loom.testing¶
Functions
|
- class loom.testing.GoldenHarness[source]¶
Bases:
objectExecutes use cases in isolation with fake repositories.
Allows injecting fake repo instances, forcing errors on specific methods, and asserting performance baselines — all without a real database.
Example:
harness = GoldenHarness() harness.inject_repo(IProductRepo, FakeProductRepo(), model=Product) harness.force_error(IProductRepo, "create", Conflict("duplicate")) result = await harness.run(CreateProductUseCase, payload={"name": "X"})
- inject_repo(interface, fake_instance, *, model=None)[source]¶
Register a fake repository instance for an interface type.
- Parameters:
- Return type:
None
- force_error(interface, method, error)[source]¶
Force a specific repository method to raise an error.
- simulate_system_error(interface, method)[source]¶
Simulate a
SystemErroron a specific repository method.
- async run(use_case_type, *, params=None, payload=None)[source]¶
Execute a use case with injected fake repositories.
- Parameters:
- Returns:
Result produced by the use case.
- Return type:
- async run_with_baseline(use_case_type, *, params=None, payload=None, name, max_ms, baseline_dir)[source]¶
Execute a use case and assert it completes within a time baseline.
Writes a
<name>.jsonfile tobaseline_dirrecording the measured duration. RaisesAssertionErrorif execution exceedsmax_ms.- Parameters:
use_case_type (type[UseCase[Any, Any, RepoFor[Any]]]) – UseCase subclass to execute.
params (dict[str, Any] | None) – Primitive parameter values keyed by name.
payload (dict[str, Any] | None) – Raw dict for
Input()command construction.name (str) – Baseline identifier used as the filename.
max_ms (float) – Maximum allowed execution duration in milliseconds.
baseline_dir (Path) – Directory where baseline JSON files are written.
- Returns:
Result produced by the use case.
- Raises:
AssertionError – If elapsed time exceeds
max_ms.- Return type:
- class loom.testing.InMemoryRepository(entity_type, *, id_field='id', creator=None)[source]¶
Bases:
Generic[T]Generic in-memory repository for testing any
msgspec.Structmodel.Stores entities in a plain dict keyed by their id field value. Provides the standard repository surface (
get_by_id,create,update,delete,list_paginated) without any database dependency.The
createmethod derives entity fields from the command automatically when nocreatorcallable is provided: fields present on the command are copied to the entity, and the id field is assigned from an internal auto-increment counter.- Parameters:
entity_type (type[T]) – The
msgspec.Structsubclass this repository stores.id_field (str) – Name of the identity field on the entity. Defaults to
"id".creator (Callable[[Any, int], T] | None) – Optional
(cmd, next_id) -> Tcallable used bycreate(). When provided, the automatic field-mapping is bypassed entirely.
Example:
repo = InMemoryRepository(Product, id_field="id") repo.seed(Product(id=1, name="Widget"), Product(id=2, name="Gadget")) harness = HttpTestHarness() harness.inject_repo(Product, repo) client = harness.build_app(interfaces=[ProductRestInterface])
- seed(*entities)[source]¶
Pre-load entities into the store.
The internal id counter is advanced past the highest integer id seen so that subsequent
create()calls do not collide.- Parameters:
*entities (T) – Entity instances to load.
- Return type:
None
Example:
repo.seed(Product(id=1, name="A"), Product(id=2, name="B"))
- async get_by_id(obj_id, profile='default')[source]¶
Return the entity with
obj_id, orNoneif not found.
- async create(cmd)[source]¶
Create and store a new entity from
cmd.If a
creatorcallable was provided at construction it is called ascreator(cmd, next_id). Otherwise, command attributes whose names match entity fields are copied automatically, and the id field is set from the internal auto-increment counter.- Parameters:
cmd (Any) – Command or payload object carrying the new entity’s data.
- Returns:
The created and stored entity.
- Return type:
T
- async update(obj_id, data)[source]¶
Update the entity at
obj_idwith fields fromdata.Only non-
Nonefields present on bothdataand the entity are overwritten; the id field is never changed.
- class loom.testing.RepositoryIntegrationHarness(*, session_manager, entities, load_order=())[source]¶
Bases:
objectTest harness for integration tests over repository implementations.
- Parameters:
session_manager (SessionManager)
entities (Mapping[str, EntityHarness])
- class loom.testing.UseCaseTest(use_case)[source]¶
Bases:
Generic[ResultT]Fluent test harness for executing UseCases without HTTP or framework overhead.
Builds and runs the real ExecutionPlan — no shortcuts or mocking of the pipeline. Designed for unit and integration tests that must exercise computes, rules, and load steps in full.
- Parameters:
use_case (UseCase[Any, ResultT]) – Constructed UseCase instance to test.
Example:
result = await ( UseCaseTest(UpdateUserUseCase(repo=fake_repo)) .with_params(user_id=1) .with_input(email="new@example.com") .run() )
- with_params(**kwargs)[source]¶
Set primitive parameter values bound by name.
- Parameters:
**kwargs (Any) – Parameter names and values matching the UseCase’s non-Input, non-Load parameters.
- Returns:
selffor chaining.- Return type:
UseCaseTest[ResultT]
- with_input(**kwargs)[source]¶
Set raw payload fields for command construction.
The payload is passed to the Command’s
from_payloadmethod. Usewith_commandif you have a pre-built Command instance.- Parameters:
**kwargs (Any) – Payload fields matching the Command struct.
- Returns:
selffor chaining.- Return type:
UseCaseTest[ResultT]
- with_command(cmd)[source]¶
Set a pre-built Command instance as the execution payload.
Serializes the command via
msgspec.to_builtinsso it is compatible with the standardfrom_payloadpipeline.- Parameters:
cmd (Any) – A
Command(msgspec.Struct) instance.- Returns:
selffor chaining.- Return type:
UseCaseTest[ResultT]
- with_loaded(entity_type, entity)[source]¶
Pre-load an entity, bypassing repository calls for this type.
- Parameters:
- Returns:
selffor chaining.- Return type:
UseCaseTest[ResultT]
- with_deps(entity_type, repo)[source]¶
Register a repository for a given entity type.
Used when the UseCase has
LoadById()steps that require a repo.with_loadedtakes precedence overwith_depsfor the same type.- Parameters:
- Returns:
selffor chaining.- Return type:
UseCaseTest[ResultT]
- with_main_repo(repo)[source]¶
Inject the main repository dependency into the UseCase instance.
This is useful for unit tests of
UseCase[TModel, TResult]where the core logic reads fromself.main_repo.- Parameters:
repo (RepoFor[Any]) – Repository instance compatible with the UseCase’s main model.
- Returns:
selffor chaining.- Return type:
UseCaseTest[ResultT]
- async run()[source]¶
Compile and execute the UseCase through the full pipeline.
- Returns:
The result produced by the UseCase.
- Raises:
loom.core.errors.RuleViolations – If one or more rule steps fail.
NotFound – If a Load step finds no entity.
loom.core.engine.compiler.CompilationError – If the UseCase fails structural validation.
- Return type:
ResultT
- property plan: ExecutionPlan¶
Compile and return the ExecutionPlan for the UseCase.
Useful for asserting plan structure in advanced test scenarios.
- Returns:
The compiled
ExecutionPlan.
- loom.testing.build_repository_harness(*, session_manager, models, repositories=None, load_order=())[source]¶
Build a repository integration harness with generic/default repositories.
- Parameters:
- Returns:
Configured repository integration harness.
- Return type:
- loom.testing.serialize_plan(plan)[source]¶
Produce a deterministic, JSON-serialisable snapshot of an
ExecutionPlan.All keys are sorted alphabetically so the output is stable across runs regardless of insertion order. Types and callables are encoded as their fully qualified
module.qualnamestring.- Parameters:
plan (ExecutionPlan) – Compiled execution plan to serialise.
- Returns:
Dictionary containing only JSON-primitive values (str, int, list, dict, None). Suitable for
json.dumpscomparison.- Return type:
Example:
snapshot = serialize_plan(compiler.get_plan(MyUseCase)) assert snapshot["use_case"] == "my_app.use_cases.MyUseCase"