Skip to content

Commit a5b128f

Browse files
authored
Introduce MockSmaEvCharger to tests (#25)
1 parent 37056e1 commit a5b128f

10 files changed

Lines changed: 143 additions & 131 deletions

File tree

custom_components/smaev/__init__.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44
import logging
55
from typing import TYPE_CHECKING
66

7-
from pysmaev.core import SmaEvCharger
8-
from pysmaev.exceptions import (
9-
SmaEvChargerAuthenticationError,
10-
SmaEvChargerConnectionError,
11-
)
7+
import pysmaev.core
8+
import pysmaev.exceptions
129

1310
from homeassistant.config_entries import ConfigEntry
1411
from homeassistant.const import (
@@ -57,16 +54,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
5754
url = f"{protocol}://{entry.data[CONF_HOST]}"
5855

5956
session = async_get_clientsession(hass, verify_ssl=entry.data[CONF_VERIFY_SSL])
60-
evcharger = SmaEvCharger(
57+
evcharger = pysmaev.core.SmaEvCharger(
6158
session, url, entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD]
6259
)
6360

6461
try:
6562
await evcharger.open()
6663
smaev_device_info = await evcharger.device_info()
67-
except SmaEvChargerConnectionError as exc:
64+
except pysmaev.exceptions.SmaEvChargerConnectionError as exc:
6865
raise ConfigEntryNotReady from exc
69-
except SmaEvChargerAuthenticationError as exc:
66+
except pysmaev.exceptions.SmaEvChargerAuthenticationError as exc:
7067
raise ConfigEntryAuthFailed from exc
7168

7269
if TYPE_CHECKING:

custom_components/smaev/config_flow.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,8 @@
55
import logging
66
from typing import Any
77

8-
from pysmaev.core import SmaEvCharger
9-
from pysmaev.exceptions import (
10-
SmaEvChargerAuthenticationError,
11-
SmaEvChargerConnectionError,
12-
)
8+
import pysmaev.core
9+
import pysmaev.exceptions
1310

1411
import voluptuous as vol
1512

@@ -49,15 +46,17 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
4946

5047
protocol = "https" if data[CONF_SSL] else "http"
5148
url = f"{protocol}://{data[CONF_HOST]}"
52-
evcharger = SmaEvCharger(session, url, data[CONF_USERNAME], data[CONF_PASSWORD])
49+
evcharger = pysmaev.core.SmaEvCharger(
50+
session, url, data[CONF_USERNAME], data[CONF_PASSWORD]
51+
)
5352

5453
errors: dict[str, str] = {}
5554
device_info: dict[str, str] = {}
5655
try:
5756
await evcharger.open()
58-
except SmaEvChargerConnectionError:
57+
except pysmaev.exceptions.SmaEvChargerConnectionError:
5958
errors[CONF_BASE] = "cannot_connect"
60-
except SmaEvChargerAuthenticationError:
59+
except pysmaev.exceptions.SmaEvChargerAuthenticationError:
6160
errors[CONF_BASE] = "invalid_auth"
6261
except Exception: # pylint: disable=broad-except
6362
_LOGGER.exception("Unexpected exception")

tests/conftest.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
import os
33
import sys
44

5+
from unittest.mock import AsyncMock
6+
57
import pytest
68
from homeassistant.const import CONF_HOST
79
from custom_components import smaev
810

11+
from pysmaev.core import SmaEvCharger
12+
913
# Tests in the dev enviromentment use the pytest_homeassistant_custom_component instead of
1014
# a cloned HA core repo for a simple and clean structure. To still test against a HA core
1115
# clone (e.g. the dev branch for which no pytest_homeassistant_custom_component exists
@@ -33,6 +37,25 @@ def auto_enable_custom_integrations(enable_custom_integrations):
3337
}
3438

3539

40+
DEVICE_INFO = {
41+
"name": "SMA EV Charger 22",
42+
"serial": "1234567890",
43+
"model": "EVC22-3AC-10",
44+
"manufacturer": "SMA",
45+
"sw_version": "1.2.23.R",
46+
}
47+
48+
49+
class MockSmaEvCharger(SmaEvCharger):
50+
"""Mocked SmaEvCharger."""
51+
52+
open = AsyncMock()
53+
request_measurements = AsyncMock()
54+
request_parameters = AsyncMock()
55+
56+
device_info = AsyncMock(return_value=DEVICE_INFO)
57+
58+
3659
@pytest.fixture(name="entry")
3760
def create_entry():
3861
"""Return mock entry."""
@@ -44,15 +67,3 @@ def create_entry():
4467
options={},
4568
)
4669
return entry
47-
48-
49-
@pytest.fixture(name="device_info")
50-
def device_info():
51-
"""Return device info."""
52-
return {
53-
"name": "SMA EV Charger 22",
54-
"serial": "1234567890",
55-
"model": "EVC22-3AC-10",
56-
"manufacturer": "SMA",
57-
"sw_version": "1.2.23.R",
58-
}

tests/test_config_flow.py

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33

44
import pytest
55

6+
import pysmaev.core
7+
import pysmaev.exceptions
8+
69
from homeassistant import config_entries, data_entry_flow
710
from homeassistant.const import CONF_HOST, CONF_BASE
811
from homeassistant.core import HomeAssistant
912

1013
from custom_components import smaev
1114
from custom_components.smaev.config_flow import SmaEvChargerConfigFlow, validate_input
1215

13-
from .conftest import CONFIG_DATA
16+
from .conftest import CONFIG_DATA, MockSmaEvCharger, DEVICE_INFO
1417

1518

1619
async def test_show_form(hass: HomeAssistant) -> None:
@@ -24,44 +27,41 @@ async def test_show_form(hass: HomeAssistant) -> None:
2427
assert result["step_id"] == "user"
2528

2629

27-
async def test_step_user(hass, device_info):
30+
@patch.object(pysmaev.core, "SmaEvCharger", MockSmaEvCharger)
31+
async def test_step_user(hass):
2832
"""Test for user step."""
29-
with (
30-
patch("pysmaev.core.SmaEvCharger.open"),
31-
patch("pysmaev.core.SmaEvCharger.device_info", return_value=device_info),
32-
):
33-
data = CONFIG_DATA.copy()
34-
result = await hass.config_entries.flow.async_init(
35-
smaev.DOMAIN, context={"source": config_entries.SOURCE_USER}, data=data
36-
)
33+
data = CONFIG_DATA.copy()
34+
result = await hass.config_entries.flow.async_init(
35+
smaev.DOMAIN, context={"source": config_entries.SOURCE_USER}, data=data
36+
)
3737

38-
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
39-
assert result["title"] == CONFIG_DATA[CONF_HOST]
40-
assert result["data"] == CONFIG_DATA
38+
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
39+
assert result["title"] == CONFIG_DATA[CONF_HOST]
40+
assert result["data"] == CONFIG_DATA
4141

4242

43-
async def test_step_user_existing_host(hass, entry, device_info):
43+
@patch.object(pysmaev.core, "SmaEvCharger", MockSmaEvCharger)
44+
async def test_step_user_existing_host(hass, entry):
4445
"""Test for user defined host already exists."""
4546
entry.add_to_hass(hass)
4647

47-
with (
48-
patch("pysmaev.core.SmaEvCharger.open"),
49-
patch("pysmaev.core.SmaEvCharger.device_info", return_value=device_info),
50-
):
51-
data = entry.data.copy()
52-
result = await hass.config_entries.flow.async_init(
53-
smaev.DOMAIN, context={"source": config_entries.SOURCE_USER}, data=data
54-
)
48+
data = entry.data.copy()
49+
result = await hass.config_entries.flow.async_init(
50+
smaev.DOMAIN, context={"source": config_entries.SOURCE_USER}, data=data
51+
)
5552

56-
assert result["type"] == data_entry_flow.FlowResultType.ABORT
57-
assert result["reason"] == "already_configured"
53+
assert result["type"] == data_entry_flow.FlowResultType.ABORT
54+
assert result["reason"] == "already_configured"
5855

5956

6057
@pytest.mark.parametrize(
6158
("error", "errors"),
6259
[
63-
(smaev.SmaEvChargerConnectionError, {CONF_BASE: "cannot_connect"}),
64-
(smaev.SmaEvChargerAuthenticationError, {CONF_BASE: "invalid_auth"}),
60+
(pysmaev.exceptions.SmaEvChargerConnectionError, {CONF_BASE: "cannot_connect"}),
61+
(
62+
pysmaev.exceptions.SmaEvChargerAuthenticationError,
63+
{CONF_BASE: "invalid_auth"},
64+
),
6565
(Exception, {CONF_BASE: "unknown"}),
6666
],
6767
)
@@ -77,7 +77,8 @@ async def test_step_user_error(hass, error, errors):
7777
assert result["errors"] == errors
7878

7979

80-
async def test_options_flow(hass, entry, device_info):
80+
@patch.object(pysmaev.core, "SmaEvCharger", MockSmaEvCharger)
81+
async def test_options_flow(hass, entry):
8182
"""Test config flow options."""
8283
entry.add_to_hass(hass)
8384
result = await hass.config_entries.options.async_init(entry.entry_id)
@@ -87,13 +88,9 @@ async def test_options_flow(hass, entry, device_info):
8788

8889
user_input = CONFIG_DATA.copy()
8990

90-
with (
91-
patch("pysmaev.core.SmaEvCharger.open"),
92-
patch("pysmaev.core.SmaEvCharger.device_info", return_value=device_info),
93-
):
94-
result = await hass.config_entries.options.async_configure(
95-
result["flow_id"], user_input=user_input
96-
)
91+
result = await hass.config_entries.options.async_configure(
92+
result["flow_id"], user_input=user_input
93+
)
9794

9895
assert result["type"] == data_entry_flow.FlowResultType.CREATE_ENTRY
9996
assert all(value == entry.data[key] for key, value in user_input.items())
@@ -102,8 +99,11 @@ async def test_options_flow(hass, entry, device_info):
10299
@pytest.mark.parametrize(
103100
("error", "errors"),
104101
[
105-
(smaev.SmaEvChargerConnectionError, {CONF_BASE: "cannot_connect"}),
106-
(smaev.SmaEvChargerAuthenticationError, {CONF_BASE: "invalid_auth"}),
102+
(pysmaev.exceptions.SmaEvChargerConnectionError, {CONF_BASE: "cannot_connect"}),
103+
(
104+
pysmaev.exceptions.SmaEvChargerAuthenticationError,
105+
{CONF_BASE: "invalid_auth"},
106+
),
107107
(Exception, {CONF_BASE: "unknown"}),
108108
],
109109
)
@@ -126,28 +126,26 @@ async def test_options_flow_errors(hass, entry, error, errors):
126126
assert result["errors"] == errors
127127

128128

129-
async def test_validate_connection(hass: HomeAssistant, device_info):
129+
async def test_validate_connection(hass: HomeAssistant):
130130
"""Test the connection validation."""
131131
data = CONFIG_DATA.copy()
132132

133-
with (
134-
patch("pysmaev.core.SmaEvCharger.open") as mock_open,
135-
patch(
136-
"pysmaev.core.SmaEvCharger.device_info", return_value=device_info
137-
) as mock_device_info,
138-
):
133+
with patch("pysmaev.core.SmaEvCharger", MockSmaEvCharger) as mock:
139134
result = await validate_input(hass, data=data)
140135

141-
assert mock_open.is_called
142-
assert mock_device_info.is_called
143-
assert result == (device_info, {})
136+
assert mock.open.is_called
137+
assert mock.device_info.is_called
138+
assert result == (DEVICE_INFO, {})
144139

145140

146141
@pytest.mark.parametrize(
147142
("error", "errors"),
148143
[
149-
(smaev.SmaEvChargerConnectionError, {CONF_BASE: "cannot_connect"}),
150-
(smaev.SmaEvChargerAuthenticationError, {CONF_BASE: "invalid_auth"}),
144+
(pysmaev.exceptions.SmaEvChargerConnectionError, {CONF_BASE: "cannot_connect"}),
145+
(
146+
pysmaev.exceptions.SmaEvChargerAuthenticationError,
147+
{CONF_BASE: "invalid_auth"},
148+
),
151149
(Exception, {CONF_BASE: "unknown"}),
152150
],
153151
)

tests/test_datetime.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
"""Test for the SMA EV Charger datetime platform."""
22
from unittest.mock import patch
33

4+
import pysmaev.core
5+
46
from homeassistant.const import STATE_UNKNOWN
57
from homeassistant.core import HomeAssistant
68

79
from custom_components.smaev import generate_smaev_entity_id
810
from custom_components.smaev.datetime import DATETIME_DESCRIPTIONS, ENTITY_ID_FORMAT
911

12+
from .conftest import MockSmaEvCharger
13+
1014

11-
async def test_setup_smaev_datetime(hass: HomeAssistant, entry, device_info):
15+
@patch.object(pysmaev.core, "SmaEvCharger", MockSmaEvCharger)
16+
async def test_setup_smaev_datetime(hass: HomeAssistant, entry):
1217
"""Test the setup of datetime."""
1318
entry.add_to_hass(hass)
14-
with (
15-
patch("pysmaev.core.SmaEvCharger.open"),
16-
patch("pysmaev.core.SmaEvCharger.device_info", return_value=device_info),
17-
):
18-
assert await hass.config_entries.async_setup(entry.entry_id)
19-
await hass.async_block_till_done()
19+
assert await hass.config_entries.async_setup(entry.entry_id)
20+
await hass.async_block_till_done()
2021

2122
for description in DATETIME_DESCRIPTIONS:
2223
if not description.entity_registry_enabled_default:

0 commit comments

Comments
 (0)