From 530552b4f5523539531a74cf0d11be13f54ae30f Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Fri, 29 Mar 2024 02:23:21 +0100 Subject: [PATCH] Use `mock_platform` for device_tracker entity component tests instead of `hass.components` (#114398) Co-authored-by: Martin Hjelmare --- tests/components/conftest.py | 22 ++++- .../device_sun_light_trigger/test_init.py | 23 +++-- tests/components/device_tracker/common.py | 99 +++++++++++++++++++ tests/components/device_tracker/test_init.py | 56 +++++------ .../custom_components/test/device_tracker.py | 99 ------------------- 5 files changed, 158 insertions(+), 141 deletions(-) delete mode 100644 tests/testing_config/custom_components/test/device_tracker.py diff --git a/tests/components/conftest.py b/tests/components/conftest.py index 0831c566666..09e74142ad3 100644 --- a/tests/components/conftest.py +++ b/tests/components/conftest.py @@ -1,16 +1,18 @@ """Fixtures for component testing.""" -from collections.abc import Generator +from collections.abc import Callable, Generator from typing import TYPE_CHECKING, Any from unittest.mock import MagicMock, patch import pytest from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant from tests.common import MockToggleEntity if TYPE_CHECKING: + from tests.components.device_tracker.common import MockScanner from tests.components.light.common import MockLight from tests.components.sensor.common import MockSensor @@ -137,3 +139,21 @@ def mock_toggle_entities() -> list[MockToggleEntity]: from tests.components.switch.common import get_mock_toggle_entities return get_mock_toggle_entities() + + +@pytest.fixture +def mock_legacy_device_scanner() -> "MockScanner": + """Return mocked legacy device scanner entity.""" + from tests.components.device_tracker.common import MockScanner + + return MockScanner() + + +@pytest.fixture +def mock_legacy_device_tracker_setup() -> ( + Callable[[HomeAssistant, "MockScanner"], None] +): + """Return setup callable for legacy device tracker setup.""" + from tests.components.device_tracker.common import mock_legacy_device_tracker_setup + + return mock_legacy_device_tracker_setup diff --git a/tests/components/device_sun_light_trigger/test_init.py b/tests/components/device_sun_light_trigger/test_init.py index 570708cec79..b373bd4401f 100644 --- a/tests/components/device_sun_light_trigger/test_init.py +++ b/tests/components/device_sun_light_trigger/test_init.py @@ -1,5 +1,6 @@ """The tests device sun light trigger component.""" +from collections.abc import Callable from datetime import datetime from unittest.mock import patch @@ -26,20 +27,24 @@ from homeassistant.core import CoreState, HomeAssistant from homeassistant.setup import async_setup_component from homeassistant.util import dt as dt_util -from tests.common import async_fire_time_changed +from tests.common import async_fire_time_changed, setup_test_component_platform +from tests.components.device_tracker.common import MockScanner +from tests.components.light.common import MockLight @pytest.fixture -async def scanner(hass, enable_custom_integrations): +async def scanner( + hass: HomeAssistant, + mock_light_entities: list[MockLight], + mock_legacy_device_scanner: MockScanner, + mock_legacy_device_tracker_setup: Callable[[HomeAssistant, MockScanner], None], +) -> None: """Initialize components.""" - scanner = await getattr(hass.components, "test.device_tracker").async_get_scanner( - None, None - ) + mock_legacy_device_tracker_setup(hass, mock_legacy_device_scanner) + mock_legacy_device_scanner.reset() + mock_legacy_device_scanner.come_home("DEV1") - scanner.reset() - scanner.come_home("DEV1") - - getattr(hass.components, "test.light").init() + setup_test_component_platform(hass, "light", mock_light_entities) with patch( "homeassistant.components.device_tracker.legacy.load_yaml_config_file", diff --git a/tests/components/device_tracker/common.py b/tests/components/device_tracker/common.py index 973eb7d8820..eb88f9dfefc 100644 --- a/tests/components/device_tracker/common.py +++ b/tests/components/device_tracker/common.py @@ -15,11 +15,16 @@ from homeassistant.components.device_tracker import ( ATTR_MAC, DOMAIN, SERVICE_SEE, + DeviceScanner, + ScannerEntity, + SourceType, ) from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.typing import GPSType from homeassistant.loader import bind_hass +from tests.common import MockPlatform, mock_platform + @callback @bind_hass @@ -51,3 +56,97 @@ def async_see( if attributes: data[ATTR_ATTRIBUTES] = attributes hass.async_create_task(hass.services.async_call(DOMAIN, SERVICE_SEE, data)) + + +class MockScannerEntity(ScannerEntity): + """Test implementation of a ScannerEntity.""" + + def __init__(self): + """Init.""" + self.connected = False + self._hostname = "test.hostname.org" + self._ip_address = "0.0.0.0" + self._mac_address = "ad:de:ef:be:ed:fe" + + @property + def source_type(self): + """Return the source type, eg gps or router, of the device.""" + return SourceType.ROUTER + + @property + def battery_level(self): + """Return the battery level of the device. + + Percentage from 0-100. + """ + return 100 + + @property + def ip_address(self) -> str: + """Return the primary ip address of the device.""" + return self._ip_address + + @property + def mac_address(self) -> str: + """Return the mac address of the device.""" + return self._mac_address + + @property + def hostname(self) -> str: + """Return hostname of the device.""" + return self._hostname + + @property + def is_connected(self): + """Return true if the device is connected to the network.""" + return self.connected + + def set_connected(self): + """Set connected to True.""" + self.connected = True + self.async_write_ha_state() + + +class MockScanner(DeviceScanner): + """Mock device scanner.""" + + def __init__(self): + """Initialize the MockScanner.""" + self.devices_home = [] + + def come_home(self, device): + """Make a device come home.""" + self.devices_home.append(device) + + def leave_home(self, device): + """Make a device leave the house.""" + self.devices_home.remove(device) + + def reset(self): + """Reset which devices are home.""" + self.devices_home = [] + + def scan_devices(self): + """Return a list of fake devices.""" + return list(self.devices_home) + + def get_device_name(self, device): + """Return a name for a mock device. + + Return None for dev1 for testing. + """ + return None if device == "DEV1" else device.lower() + + +def mock_legacy_device_tracker_setup( + hass: HomeAssistant, legacy_device_scanner: MockScanner +) -> None: + """Mock legacy device tracker platform setup.""" + + async def _async_get_scanner(hass, config) -> MockScanner: + """Return the test scanner.""" + return legacy_device_scanner + + mocked_platform = MockPlatform() + mocked_platform.async_get_scanner = _async_get_scanner + mock_platform(hass, "test.device_tracker", mocked_platform) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 3b95fc9582c..b36ffdf14f6 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -31,6 +31,7 @@ from homeassistant.setup import async_setup_component import homeassistant.util.dt as dt_util from . import common +from .common import MockScanner, mock_legacy_device_tracker_setup from tests.common import ( assert_setup_component, @@ -58,6 +59,14 @@ def mock_yaml_devices(hass): os.remove(yaml_devices) +@pytest.fixture(autouse=True) +def _mock_legacy_device_tracker_setup( + hass: HomeAssistant, mock_legacy_device_scanner: MockScanner +) -> None: + """Mock legacy device tracker setup.""" + mock_legacy_device_tracker_setup(hass, mock_legacy_device_scanner) + + async def test_is_on(hass: HomeAssistant) -> None: """Test is_on method.""" entity_id = f"{const.DOMAIN}.test" @@ -99,9 +108,7 @@ async def test_reading_broken_yaml_config(hass: HomeAssistant) -> None: assert res[0].dev_id == "my_device" -async def test_reading_yaml_config( - hass: HomeAssistant, yaml_devices, enable_custom_integrations: None -) -> None: +async def test_reading_yaml_config(hass: HomeAssistant, yaml_devices) -> None: """Test the rendering of the YAML configuration.""" dev_id = "test" device = legacy.Device( @@ -179,9 +186,7 @@ async def test_duplicate_mac_dev_id(mock_warning, hass: HomeAssistant) -> None: assert "Duplicate device IDs" in args[0], "Duplicate device IDs warning expected" -async def test_setup_without_yaml_file( - hass: HomeAssistant, yaml_devices, enable_custom_integrations: None -) -> None: +async def test_setup_without_yaml_file(hass: HomeAssistant, yaml_devices) -> None: """Test with no YAML file.""" with assert_setup_component(1, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) @@ -280,13 +285,11 @@ async def test_discover_platform_missing_platform( async def test_update_stale( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device], - enable_custom_integrations: None, + mock_legacy_device_scanner: MockScanner, ) -> None: """Test stalled update.""" - - scanner = getattr(hass.components, "test.device_tracker").SCANNER - scanner.reset() - scanner.come_home("DEV1") + mock_legacy_device_scanner.reset() + mock_legacy_device_scanner.come_home("DEV1") now = dt_util.utcnow() register_time = datetime(now.year + 1, 9, 15, 23, tzinfo=dt_util.UTC) @@ -313,7 +316,7 @@ async def test_update_stale( assert hass.states.get("device_tracker.dev1").state == STATE_HOME - scanner.leave_home("DEV1") + mock_legacy_device_scanner.leave_home("DEV1") with patch( "homeassistant.components.device_tracker.legacy.dt_util.utcnow", @@ -328,7 +331,6 @@ async def test_update_stale( async def test_entity_attributes( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device], - enable_custom_integrations: None, ) -> None: """Test the entity attributes.""" devices = mock_device_tracker_conf @@ -362,9 +364,7 @@ async def test_entity_attributes( @patch("homeassistant.components.device_tracker.legacy.DeviceTracker.async_see") -async def test_see_service( - mock_see, hass: HomeAssistant, enable_custom_integrations: None -) -> None: +async def test_see_service(mock_see, hass: HomeAssistant) -> None: """Test the see service with a unicode dev_id and NO MAC.""" with assert_setup_component(1, device_tracker.DOMAIN): assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) @@ -395,7 +395,6 @@ async def test_see_service( async def test_see_service_guard_config_entry( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device], - enable_custom_integrations: None, ) -> None: """Test the guard if the device is registered in the entity registry.""" mock_entry = Mock() @@ -416,7 +415,6 @@ async def test_see_service_guard_config_entry( async def test_new_device_event_fired( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device], - enable_custom_integrations: None, ) -> None: """Test that the device tracker will fire an event.""" with assert_setup_component(1, device_tracker.DOMAIN): @@ -451,7 +449,6 @@ async def test_new_device_event_fired( async def test_duplicate_yaml_keys( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device], - enable_custom_integrations: None, ) -> None: """Test that the device tracker will not generate invalid YAML.""" devices = mock_device_tracker_conf @@ -471,7 +468,6 @@ async def test_duplicate_yaml_keys( async def test_invalid_dev_id( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device], - enable_custom_integrations: None, ) -> None: """Test that the device tracker will not allow invalid dev ids.""" devices = mock_device_tracker_conf @@ -485,9 +481,7 @@ async def test_invalid_dev_id( assert not devices -async def test_see_state( - hass: HomeAssistant, yaml_devices, enable_custom_integrations: None -) -> None: +async def test_see_state(hass: HomeAssistant, yaml_devices) -> None: """Test device tracker see records state correctly.""" assert await async_setup_component(hass, device_tracker.DOMAIN, TEST_PLATFORM) await hass.async_block_till_done() @@ -527,7 +521,7 @@ async def test_see_state( async def test_see_passive_zone_state( hass: HomeAssistant, mock_device_tracker_conf: list[legacy.Device], - enable_custom_integrations: None, + mock_legacy_device_scanner: MockScanner, ) -> None: """Test that the device tracker sets gps for passive trackers.""" now = dt_util.utcnow() @@ -547,9 +541,8 @@ async def test_see_passive_zone_state( await async_setup_component(hass, zone.DOMAIN, {"zone": zone_info}) await hass.async_block_till_done() - scanner = getattr(hass.components, "test.device_tracker").SCANNER - scanner.reset() - scanner.come_home("dev1") + mock_legacy_device_scanner.reset() + mock_legacy_device_scanner.come_home("dev1") with ( patch( @@ -581,7 +574,7 @@ async def test_see_passive_zone_state( assert attrs.get("gps_accuracy") == 0 assert attrs.get("source_type") == SourceType.ROUTER - scanner.leave_home("dev1") + mock_legacy_device_scanner.leave_home("dev1") with patch( "homeassistant.components.device_tracker.legacy.dt_util.utcnow", @@ -668,12 +661,11 @@ async def test_bad_platform(hass: HomeAssistant) -> None: async def test_adding_unknown_device_to_config( mock_device_tracker_conf: list[legacy.Device], hass: HomeAssistant, - enable_custom_integrations: None, + mock_legacy_device_scanner: MockScanner, ) -> None: """Test the adding of unknown devices to configuration file.""" - scanner = getattr(hass.components, "test.device_tracker").SCANNER - scanner.reset() - scanner.come_home("DEV1") + mock_legacy_device_scanner.reset() + mock_legacy_device_scanner.come_home("DEV1") await async_setup_component( hass, device_tracker.DOMAIN, {device_tracker.DOMAIN: {CONF_PLATFORM: "test"}} diff --git a/tests/testing_config/custom_components/test/device_tracker.py b/tests/testing_config/custom_components/test/device_tracker.py deleted file mode 100644 index 11eb366f2fc..00000000000 --- a/tests/testing_config/custom_components/test/device_tracker.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Provide a mock device scanner.""" - -from homeassistant.components.device_tracker import DeviceScanner -from homeassistant.components.device_tracker.config_entry import ScannerEntity -from homeassistant.components.device_tracker.const import SourceType - - -async def async_get_scanner(hass, config): - """Return a mock scanner.""" - return SCANNER - - -class MockScannerEntity(ScannerEntity): - """Test implementation of a ScannerEntity.""" - - def __init__(self): - """Init.""" - self.connected = False - self._hostname = "test.hostname.org" - self._ip_address = "0.0.0.0" - self._mac_address = "ad:de:ef:be:ed:fe" - - @property - def source_type(self): - """Return the source type, eg gps or router, of the device.""" - return SourceType.ROUTER - - @property - def battery_level(self): - """Return the battery level of the device. - - Percentage from 0-100. - """ - return 100 - - @property - def ip_address(self) -> str: - """Return the primary ip address of the device.""" - return self._ip_address - - @property - def mac_address(self) -> str: - """Return the mac address of the device.""" - return self._mac_address - - @property - def hostname(self) -> str: - """Return hostname of the device.""" - return self._hostname - - @property - def is_connected(self): - """Return true if the device is connected to the network.""" - return self.connected - - def set_connected(self): - """Set connected to True.""" - self.connected = True - self.async_schedule_update_ha_state() - - -async def async_setup_entry(hass, config_entry, async_add_entities): - """Set up the config entry.""" - entity = MockScannerEntity() - async_add_entities([entity]) - - -class MockScanner(DeviceScanner): - """Mock device scanner.""" - - def __init__(self): - """Initialize the MockScanner.""" - self.devices_home = [] - - def come_home(self, device): - """Make a device come home.""" - self.devices_home.append(device) - - def leave_home(self, device): - """Make a device leave the house.""" - self.devices_home.remove(device) - - def reset(self): - """Reset which devices are home.""" - self.devices_home = [] - - def scan_devices(self): - """Return a list of fake devices.""" - return list(self.devices_home) - - def get_device_name(self, device): - """Return a name for a mock device. - - Return None for dev1 for testing. - """ - return None if device == "DEV1" else device.lower() - - -SCANNER = MockScanner()