From c0bef5156360b8981be9f591ab923d09f23bf683 Mon Sep 17 00:00:00 2001 From: Renat Sibgatulin Date: Sat, 9 Aug 2025 00:01:39 +0200 Subject: [PATCH] Refactor airq tests to mock the API class in a fixture (#149712) --- tests/components/airq/conftest.py | 26 +++++++++++ tests/components/airq/test_config_flow.py | 56 +++++++++++------------ tests/components/airq/test_coordinator.py | 38 +++++++-------- 3 files changed, 68 insertions(+), 52 deletions(-) diff --git a/tests/components/airq/conftest.py b/tests/components/airq/conftest.py index a132153a76f..52d7fc77eb4 100644 --- a/tests/components/airq/conftest.py +++ b/tests/components/airq/conftest.py @@ -5,6 +5,8 @@ from unittest.mock import AsyncMock, patch import pytest +from .common import TEST_DEVICE_DATA, TEST_DEVICE_INFO + @pytest.fixture def mock_setup_entry() -> Generator[AsyncMock]: @@ -13,3 +15,27 @@ def mock_setup_entry() -> Generator[AsyncMock]: "homeassistant.components.airq.async_setup_entry", return_value=True ) as mock_setup_entry: yield mock_setup_entry + + +@pytest.fixture +def mock_airq(): + """Mock the aioairq.AirQ object. + + The integration imports it in two places: in coordinator and config_flow. + """ + + with ( + patch( + "homeassistant.components.airq.coordinator.AirQ", + autospec=True, + ) as mock_airq_class, + patch( + "homeassistant.components.airq.config_flow.AirQ", + new=mock_airq_class, + ), + ): + airq = mock_airq_class.return_value + # Pre-configure default mock values for setup + airq.fetch_device_info = AsyncMock(return_value=TEST_DEVICE_INFO) + airq.get_latest_data = AsyncMock(return_value=TEST_DEVICE_DATA) + yield airq diff --git a/tests/components/airq/test_config_flow.py b/tests/components/airq/test_config_flow.py index 95c22cb12c8..66cacecdaaa 100644 --- a/tests/components/airq/test_config_flow.py +++ b/tests/components/airq/test_config_flow.py @@ -1,7 +1,7 @@ """Test the air-Q config flow.""" import logging -from unittest.mock import patch +from unittest.mock import AsyncMock from aioairq import InvalidAuth from aiohttp.client_exceptions import ClientConnectionError @@ -29,7 +29,11 @@ DEFAULT_OPTIONS = { } -async def test_form(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> None: +async def test_form( + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + mock_airq: AsyncMock, +) -> None: """Test we get the form.""" caplog.set_level(logging.DEBUG) result = await hass.config_entries.flow.async_init( @@ -38,53 +42,49 @@ async def test_form(hass: HomeAssistant, caplog: pytest.LogCaptureFixture) -> No assert result["type"] is FlowResultType.FORM assert result["errors"] is None - with ( - patch("aioairq.AirQ.validate"), - patch("aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - TEST_USER_DATA, - ) - await hass.async_block_till_done() - assert f"Creating an entry for {TEST_DEVICE_INFO['name']}" in caplog.text + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + TEST_USER_DATA, + ) + await hass.async_block_till_done() + assert f"Creating an entry for {TEST_DEVICE_INFO['name']}" in caplog.text assert result2["type"] is FlowResultType.CREATE_ENTRY assert result2["title"] == TEST_DEVICE_INFO["name"] assert result2["data"] == TEST_USER_DATA -async def test_form_invalid_auth(hass: HomeAssistant) -> None: +async def test_form_invalid_auth(hass: HomeAssistant, mock_airq: AsyncMock) -> None: """Test we handle invalid auth.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("aioairq.AirQ.validate", side_effect=InvalidAuth): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], TEST_USER_DATA | {CONF_PASSWORD: "wrong_password"} - ) + mock_airq.validate.side_effect = InvalidAuth + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_USER_DATA | {CONF_PASSWORD: "wrong_password"} + ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "invalid_auth"} -async def test_form_cannot_connect(hass: HomeAssistant) -> None: +async def test_form_cannot_connect(hass: HomeAssistant, mock_airq: AsyncMock) -> None: """Test we handle cannot connect error.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("aioairq.AirQ.validate", side_effect=ClientConnectionError): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], TEST_USER_DATA - ) + mock_airq.validate.side_effect = ClientConnectionError + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_USER_DATA + ) assert result2["type"] is FlowResultType.FORM assert result2["errors"] == {"base": "cannot_connect"} -async def test_duplicate_error(hass: HomeAssistant) -> None: +async def test_duplicate_error(hass: HomeAssistant, mock_airq: AsyncMock) -> None: """Test that errors are shown when duplicates are added.""" MockConfigEntry( data=TEST_USER_DATA, @@ -96,13 +96,9 @@ async def test_duplicate_error(hass: HomeAssistant) -> None: DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with ( - patch("aioairq.AirQ.validate"), - patch("aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO), - ): - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], TEST_USER_DATA - ) + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], TEST_USER_DATA + ) assert result2["type"] is FlowResultType.ABORT assert result2["reason"] == "already_configured" diff --git a/tests/components/airq/test_coordinator.py b/tests/components/airq/test_coordinator.py index 6512d60ddbe..f45986df61d 100644 --- a/tests/components/airq/test_coordinator.py +++ b/tests/components/airq/test_coordinator.py @@ -1,7 +1,7 @@ """Test the air-Q coordinator.""" import logging -from unittest.mock import patch +from unittest.mock import AsyncMock import pytest @@ -32,7 +32,9 @@ STATUS_WARMUP = { async def test_logging_in_coordinator_first_update_data( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + mock_airq: AsyncMock, ) -> None: """Test that the first AirQCoordinator._async_update_data call logs necessary setup. @@ -48,11 +50,7 @@ async def test_logging_in_coordinator_first_update_data( assert "name" not in coordinator.device_info # First call: fetch missing device info - with ( - patch("aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO), - patch("aioairq.AirQ.get_latest_data", return_value=TEST_DEVICE_DATA), - ): - await coordinator._async_update_data() + await coordinator._async_update_data() # check that the missing name is logged... assert ( @@ -71,7 +69,9 @@ async def test_logging_in_coordinator_first_update_data( async def test_logging_in_coordinator_subsequent_update_data( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + mock_airq: AsyncMock, ) -> None: """Test that the second AirQCoordinator._async_update_data call has nothing to log. @@ -83,11 +83,7 @@ async def test_logging_in_coordinator_subsequent_update_data( coordinator = AirQCoordinator(hass, MOCKED_ENTRY) coordinator.device_info.update(DeviceInfo(**TEST_DEVICE_INFO)) - with ( - patch("aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO), - patch("aioairq.AirQ.get_latest_data", return_value=TEST_DEVICE_DATA), - ): - await coordinator._async_update_data() + await coordinator._async_update_data() # check that the name _is not_ missing assert "name" in coordinator.device_info # and that nothing of the kind is logged @@ -102,19 +98,17 @@ async def test_logging_in_coordinator_subsequent_update_data( async def test_logging_when_warming_up_sensor_present( - hass: HomeAssistant, caplog: pytest.LogCaptureFixture + hass: HomeAssistant, + caplog: pytest.LogCaptureFixture, + mock_airq: AsyncMock, ) -> None: """Test that warming up sensors are logged.""" caplog.set_level(logging.DEBUG) coordinator = AirQCoordinator(hass, MOCKED_ENTRY) - with ( - patch("aioairq.AirQ.fetch_device_info", return_value=TEST_DEVICE_INFO), - patch( - "aioairq.AirQ.get_latest_data", - return_value=TEST_DEVICE_DATA | {"Status": STATUS_WARMUP}, - ), - ): - await coordinator._async_update_data() + mock_airq.get_latest_data.return_value = TEST_DEVICE_DATA | { + "Status": STATUS_WARMUP + } + await coordinator._async_update_data() assert ( f"Following sensors are still warming up: {set(STATUS_WARMUP.keys())}" in caplog.text