Skip to content

Add roborock.testing — stateful device simulators for integration testing#860

Open
allenporter wants to merge 5 commits into
Python-roborock:mainfrom
allenporter:testing-library
Open

Add roborock.testing — stateful device simulators for integration testing#860
allenporter wants to merge 5 commits into
Python-roborock:mainfrom
allenporter:testing-library

Conversation

@allenporter

@allenporter allenporter commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Introduces a new roborock.testing package that provides stateful firmware simulators, fake transport channels, and cloud environment fakes. This allows downstream consumers (like the Home Assistant integration) to write high-fidelity integration tests using the real client library classes instead of fragile top-level mocks.

Motivation

Today, testing against this library requires mocking at the Python API level (patching methods, return values, etc.), which is brittle and doesn't exercise the real protocol handling, message encoding, or trait update logic. This package fakes communication at two well-defined boundaries:

  1. HTTP APIaioresponses intercepts network requests so the real RoborockApiClient executes fully against fake endpoints.
  2. Plaintext RPC — Device communication is intercepted at the RoborockMessage level. The real V1Channel, subscribers, and DPS listeners all run under test.

Architecture

┌────────────────────────────────────────────────────────┐
│               TESTED CLIENT (REAL CODE)                │
│                                                        │
│  RoborockDevice / Traits / V1RpcChannel / V1Channel    │
└──────────────────────────┬─────────────────────────────┘
                           │
                 ROBOROCKMESSAGE PAYLOADS
                 (Plaintext JSON commands)
                           │
┌──────────────────────────▼─────────────────────────────┐
│                 SIMULATOR (TEST FAKE)                  │
│                                                        │
│  FakeChannel (Intercepts publish/subscribe)            │
│  RoborockDeviceSimulator (Stateful firmware simulator) │
└────────────────────────────────────────────────────────┘

New Files

roborock/testing/

File Description
__init__.py Package exports and architecture documentation
channel.py FakeChannel — in-memory transport with mock attributes for failure injection and call inspection
simulator.py RoborockDeviceSimulator — base class with optional local/MQTT channels
v1_simulator.py V1VacuumSimulator — stateful V1 firmware simulator with command handlers for get_status, app_start, app_stop, set_custom_mode, consumables, DND, etc.
cloud.py FakeRoborockCloud — cloud environment orchestrator with FakeUserState, FakeWebApiClient, and patch_device_manager() context manager

tests/testing/

File Tests
test_channel.py FakeChannel subscribe/unsubscribe/publish/notify
test_cloud.py Device discovery, unsupported protocol errors, login error overrides, dynamic device addition
test_v1_simulator.py Consumable refresh/reset, DND refresh, fan speed changes, clean summary, state transition lifecycle, push update propagation, custom handler overrides

Modified

File Change
tests/fixtures/channel_fixtures.py Now re-exports FakeChannel from roborock.testing instead of defining it locally

Usage Example

from roborock.testing import FakeRoborockCloud, V1VacuumSimulator

async def test_vacuum_cleaning():
    cloud = FakeRoborockCloud()
    fake_device = V1VacuumSimulator(duid="living_room_s7", battery=100)
    cloud.add_device(fake_device)

    with cloud.patch_device_manager():
        manager = await create_device_manager(
            user_params=UserParams(username="test_user", user_data=USER_DATA),
            cache=InMemoryCache(),
        )
        devices = await manager.get_devices()

    device = devices[0]
    await device.v1_properties.command.send("app_start")
    assert fake_device.state == RoborockStateCode.cleaning

Future Work

Migrate existing tests to use roborock.testing

An earlier review identified which existing test suites should migrate to use these fakes vs. remain as-is:

High priority — should migrate:

  • tests/devices/test_device_manager.py — Currently mocks create_v1_channel and create_mqtt_channel with fragile send_command side-effect arrays. Using patch_device_manager() with a V1VacuumSimulator would eliminate the brittle handshake sequence mocking entirely.
  • tests/devices/test_v1_device.py — Manually stubs subscribe, connection, and channel creation with AsyncMock. Using a real V1Channel bound to FakeChannel would test actual client routing logic.

Medium priority — optional migration:

  • Trait-specific unit tests (e.g. test_status.py, test_consumable.py) — Direct mocks are fine for parsing edge cases, but integration-level tests using the simulator would complement them.

Should remain unchanged:

  • E2E / session tests (tests/e2e/) — These verify raw protocol byte parsing, TCP framing, and cryptography at Layer 1. Our fakes operate above that layer, so these tests serve a different purpose.

Additional enhancements

  • A01/B01 simulatorsRoborockDeviceSimulator supports has_local_channel=False for MQTT-only protocols, but no A01 (Zeo/Dyad) or B01 (Q7/Q10) simulators exist yet. patch_device_manager() currently raises NotImplementedError for these.
  • More V1 command handlers — Only core commands are implemented. Future handlers could cover room-specific cleaning (app_segment_clean), map operations, volume control, etc.
  • More trait coverage — Tests currently cover status, consumables, DND, clean summary, and command. Additional traits like rooms, maps, wash towel mode, and dust collection mode could be exercised.
  • Multi-device scenarios — Testing multiple devices registered simultaneously, with different protocols or configurations.
  • Error simulation — More granular transport error scenarios (timeouts, partial responses, reconnection behavior).

Copilot AI review requested due to automatic review settings July 2, 2026 22:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new roborock.testing package intended to provide stateful device simulators, fake transport channels, and a fake cloud environment so downstream projects can write higher-fidelity integration tests against the real client codepaths.

Changes:

  • Added roborock.testing fakes: FakeChannel, FakeRoborockCloud, RoborockDeviceSimulator, and a V1 stateful firmware simulator (V1VacuumSimulator).
  • Added a new tests/testing/ test suite validating the new fakes/simulators and their interaction with the real device manager/client traits.
  • Modified test fixtures to (intendedly) reuse the shared FakeChannel rather than maintaining a local copy.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
roborock/testing/__init__.py Exposes the new testing fakes/simulators and documents intended architecture/usage.
roborock/testing/channel.py Adds FakeChannel transport implementation for publish/subscribe testing and simulators.
roborock/testing/simulator.py Adds base stateful simulator that wires fake channels to per-protocol _handle_publish logic.
roborock/testing/v1_simulator.py Adds a V1 vacuum firmware simulator with handlers for core RPCs and DPS push updates.
roborock/testing/cloud.py Adds a fake cloud orchestrator that patches HTTP + channel factories for integration-style tests.
tests/testing/test_channel.py New tests for FakeChannel subscribe/unsubscribe/notify/publish capturing.
tests/testing/test_cloud.py New tests for fake-cloud login/home/device discovery and protocol unsupported behavior.
tests/testing/test_v1_simulator.py New integration tests validating trait behavior via the V1 simulator.
tests/testing/__init__.py Adds the tests.testing package marker.
tests/fixtures/channel_fixtures.py Fixture file changed; currently ends up empty and needs to re-export FakeChannel (or imports updated).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread roborock/testing/channel.py
Comment thread roborock/testing/channel.py Outdated
Comment thread roborock/testing/simulator.py
Comment thread roborock/testing/simulator.py Outdated
Introduces a new roborock.testing package that provides stateful firmware
simulators, fake transport channels, and cloud environment fakes. This
allows downstream consumers (like the Home Assistant integration) to write
high-fidelity integration tests using the real client library classes
instead of fragile top-level mocks.

New modules:
- channel.py: FakeChannel in-memory transport implementing Channel protocol
- simulator.py: RoborockDeviceSimulator base class
- v1_simulator.py: V1VacuumSimulator with stateful command handlers
- cloud.py: FakeRoborockCloud with HTTP endpoint mocking

Tests split by module:
- test_channel.py: FakeChannel subscribe/publish/notify
- test_cloud.py: Discovery, login errors, dynamic device addition
- test_v1_simulator.py: Trait refresh/reset, state transitions, push updates
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants