Testing Utilities¶
Reference for the cosalette.testing package — test doubles, factories, and
pytest fixtures for testing cosalette applications.
Test Harness¶
cosalette.testing.AppHarness
dataclass
¶
AppHarness(
app: App,
mqtt: MockMqttClient,
clock: FakeClock,
settings: Settings,
shutdown_event: Event,
)
Test harness wrapping App with pre-configured test doubles.
Provides unified access to App, MockMqttClient, FakeClock, Settings, and a shutdown Event — eliminating boilerplate in integration-style tests.
Usage::
harness = AppHarness.create()
@harness.app.device("sensor")
async def sensor(ctx):
...
# Run with auto-shutdown after device_called event:
await harness.run()
See Also
ADR-007 for testing strategy decisions.
create
classmethod
¶
create(
*,
name: str = "testapp",
version: str = "1.0.0",
dry_run: bool = False,
lifespan: LifespanFunc | None = None,
store: Store | None = None,
**settings_overrides: Any,
) -> Self
Create a harness with fresh test doubles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
App name. |
'testapp'
|
version
|
str
|
App version. |
'1.0.0'
|
dry_run
|
bool
|
When True, forward to App for dry-run adapter variants. |
False
|
lifespan
|
LifespanFunc | None
|
Optional lifespan context manager forwarded to
:class: |
None
|
store
|
Store | None
|
Optional :class: |
None
|
**settings_overrides
|
Any
|
Forwarded to :func: |
{}
|
Returns:
| Type | Description |
|---|---|
Self
|
A fully wired :class: |
Source code in packages/src/cosalette/testing/_harness.py
run
async
¶
Run _run_async with the harness's test doubles.
Clock¶
cosalette.testing.FakeClock
dataclass
¶
Test double for ClockPort.
Attributes:
| Name | Type | Description |
|---|---|---|
_time |
float
|
The current "now" value returned by |
Example::
clock = FakeClock(42.0)
assert clock.now() == 42.0
clock._time = 99.0
assert clock.now() == 99.0
now
¶
sleep
async
¶
Advance virtual time by seconds with no real delay.
Allows tests to exercise sleep-dependent code paths
without wall-clock waiting. The asyncio.sleep(0)
yields to the event loop so concurrent tasks interleave
correctly.
Source code in packages/src/cosalette/testing/_clock.py
MQTT Test Doubles¶
cosalette.testing.MockMqttClient
dataclass
¶
MockMqttClient(
published: list[tuple[str, str, bool, int]] = list(),
subscriptions: list[str] = list(),
raise_on_publish: Exception | None = None,
)
In-memory test double that records MQTT interactions.
Records publishes and subscriptions for assertion. Supports
callback registration and simulated message delivery via
deliver().
publish
async
¶
Record a publish call, or raise if raise_on_publish is set.
Source code in packages/src/cosalette/_mqtt.py
subscribe
async
¶
on_message
¶
on_message(callback: MessageCallback) -> None
deliver
async
¶
reset
¶
Clear all recorded data, callbacks, and failure injection.
get_messages_for
¶
Return (payload, retain, qos) tuples for topic.
Source code in packages/src/cosalette/_mqtt.py
cosalette.testing.NullMqttClient
dataclass
¶
Silent no-op MQTT adapter.
Every method is a no-op that logs at DEBUG level. Useful as a default when MQTT is not configured.
publish
async
¶
Silently discard a publish request.
Source code in packages/src/cosalette/_mqtt.py
Settings Factory¶
cosalette.testing.make_settings
¶
make_settings(**overrides: Any) -> Settings
Create a Settings instance with sensible test defaults.
Instantiates an :class:_IsolatedSettings subclass whose only
configuration source is init_settings. This means the
factory ignores os.environ, .env files, and secret
directories — tests see only model defaults plus any explicit
overrides.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**overrides
|
Any
|
Keyword arguments forwarded to the |
{}
|
Returns:
| Type | Description |
|---|---|
Settings
|
A fully initialised :class: |
Example::
settings = make_settings()
assert settings.mqtt.host == "localhost"
from cosalette._settings import MqttSettings
custom = make_settings(mqtt=MqttSettings(host="broker.test"))
assert custom.mqtt.host == "broker.test"
Source code in packages/src/cosalette/testing/_settings.py
Pytest Fixtures¶
The cosalette.testing package registers a
pytest plugin
via the pytest11 entry point. The fixtures below are available
automatically when cosalette is installed:
| Fixture | Type | Description |
|---|---|---|
mock_mqtt |
MockMqttClient |
In-memory MQTT client for capturing published messages |
fake_clock |
FakeClock |
Deterministic clock starting at 0.0 |
device_context |
DeviceContext |
Pre-wired context with mock_mqtt and fake_clock |
All fixtures are function-scoped. Import them by name — no explicit import needed.
MemoryStore¶
cosalette.MemoryStore
¶
In-memory store backed by a plain dict.
Both load and save deep-copy data so that callers cannot
mutate internal state by accident. Designed for tests — mirrors
the FakeStorage pattern from gas2mqtt.
Parameters¶
initial: Optional seed data. The mapping is deep-copied on construction.
Source code in packages/src/cosalette/_stores.py
load
¶
Return a deep copy of the stored dict, or None.
MemoryStore is the recommended test double for persistence. It stores
data in an in-memory dictionary, avoiding filesystem access in tests.