Skip to content

Testing↲

Tests use pytest with parametrized fixtures. The test suite verifies API requests made by pyvesync devices at two levels.

Running Tests↲

# Run all tests
pytest

# Run a specific test file
pytest src/tests/test_outlets.py

# Run with tox (all environments)
tox

# Individual tox environments
tox -e 3.11     # pytest with Python 3.11
tox -e 3.12     # pytest with Python 3.12
tox -e 3.13     # pytest with Python 3.13
tox -e lint     # pylint
tox -e flake8   # flake8 + pydocstrings
tox -e mypy     # type checking
tox -e ruff     # ruff linting

Test Architecture↲

TestBase - Device Method Tests↲

TestBase in src/tests/base_test_cases.py patches VeSync.async_call_api() to mock all device method calls. This is the primary testing approach for device tests. The mock intercepts the call before any HTTP request is made, capturing the url, method, json_object, and headers arguments.

The test flow:

  1. Set mock_api.return_value to the response from the call_json_* module
  2. Instantiate device via self.get_device(product_type, setup_entry)
  3. Call the device method (e.g., outlet_obj.turn_on())
  4. parse_args(self.mock_api) extracts the captured call_api arguments
  5. assert_test() scrubs sensitive data via api_scrub(), then compares against the existing YAML fixture

TestApiFunc - HTTP-level Tests↲

TestApiFunc patches aiohttp.ClientSession directly, testing the full HTTP request/response cycle. Uses AiohttpMockSession from aiohttp_mocker.py to simulate aiohttp responses. Used for login tests and API error handling (rate limits, server errors, status codes).

Parametrized Test Generation↲

Tests are auto-parametrized by conftest.py:pytest_generate_tests() based on class attributes:

class TestOutlets(TestBase):
    device = 'outlets'                    # Device category name
    outlets = call_json_outlets.OUTLETS   # List of setup_entry strings
    base_methods = [['turn_on'], ['turn_off']]  # Methods tested on ALL devices
    device_methods = {                    # Methods tested on SPECIFIC devices
        'ESW15-USA': [['turn_on_nightlight'], ['get_weekly_energy']],
    }

This generates two test functions:

  • test_details(setup_entry, method) - Tests get_details() request against YAML fixtures.
  • test_methods(setup_entry, method) - Tests each method's request against YAML fixtures.

Test IDs follow the pattern {device}.{setup_entry}.{method}, where the method is the list entry from the class attributes (e.g., outlets.ESW15-USA.['turn_on']). The test_details IDs end in .update. Use -k to select tests, e.g. pytest -k "ESW15-USA and turn_on".

YAML API Fixtures↲

API requests are recorded and verified via YAML files in src/tests/api/{module}/{setup_entry}.yaml. Each YAML file maps method names to the full request captured from the mocked call_api():

turn_off:
  headers:
    Content-Type: application/json; charset=UTF-8
    User-Agent: okhttp/3.12.1
  json_object:
    cid: ESO15-TB-CID
    method: deviceStatus
    status: 'off'
    switchNo: '1'
    token: sample_tk
    # ... remaining request keys
  method: post
  url: /cloud/v1/deviceManaged/deviceStatus

Sensitive values (tokens, account IDs, UUIDs) are normalized to defaults by api_scrub() before comparison or writing.

Writing API Fixtures↲

To generate or update YAML fixtures for new devices:

# Write fixtures that don't exist yet (does not overwrite existing)
pytest src/tests/test_outlets.py --write_api

# Overwrite existing fixtures (use with caution)
pytest src/tests/test_outlets.py --write_api --overwrite

Both flags ask for confirmation. In a non-interactive shell set PYTEST_CONFIRM=1 to skip the prompt. A missing fixture makes the test fail until it is written.

Adding Tests for a New Device↲

  1. call_json_{device_type}.py: The setup entry is added to the module's device list automatically from device_map.*_modules. Add response to DETAILS_RESPONSES[setup_entry]. Add any non-default method responses to METHOD_RESPONSES[setup_entry].

  2. test_{device_type}.py: If the device uses existing base/device methods, it is automatically included through parametrization. Add device-specific methods to device_methods dict if needed.

  3. Run pytest --write_api to generate YAML fixtures for new devices.