diff --git a/CODEOWNERS b/CODEOWNERS
index b53e0a929bb52edb07cd2141a2e55444730ae969..c9b3a53184fd5150304e892eac1d18829d58e0b3 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -433,6 +433,7 @@ build.json @home-assistant/supervisor
 /homeassistant/components/evil_genius_labs/ @balloob
 /tests/components/evil_genius_labs/ @balloob
 /homeassistant/components/evohome/ @zxdavb
+/tests/components/evohome/ @zxdavb
 /homeassistant/components/ezviz/ @RenierM26 @baqs
 /tests/components/ezviz/ @RenierM26 @baqs
 /homeassistant/components/faa_delays/ @ntilley905
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index c7934e7fc84d96276964e928a14267dc5b6aa4f8..e55efb78abbf44c8e5b89423fee4bba5e2c0c9ae 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -717,6 +717,9 @@ eternalegypt==0.0.16
 # homeassistant.components.eufylife_ble
 eufylife-ble-client==0.1.8
 
+# homeassistant.components.evohome
+evohome-async==0.4.20
+
 # homeassistant.components.bryant_evolution
 evolutionhttp==0.0.18
 
diff --git a/tests/components/evohome/__init__.py b/tests/components/evohome/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..588e0f617466113e6c0307ad2b0cbc0e313d9ce7
--- /dev/null
+++ b/tests/components/evohome/__init__.py
@@ -0,0 +1 @@
+"""The tests for the evohome integration."""
diff --git a/tests/components/evohome/conftest.py b/tests/components/evohome/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..260330896b79db3d32f19725570fb792576ff6d0
--- /dev/null
+++ b/tests/components/evohome/conftest.py
@@ -0,0 +1,111 @@
+"""Fixtures and helpers for the evohome tests."""
+
+from __future__ import annotations
+
+from datetime import datetime, timedelta
+from typing import Any, Final
+from unittest.mock import MagicMock, patch
+
+from aiohttp import ClientSession
+from evohomeasync2 import EvohomeClient
+from evohomeasync2.broker import Broker
+import pytest
+
+from homeassistant.components.evohome import CONF_PASSWORD, CONF_USERNAME, DOMAIN
+from homeassistant.core import HomeAssistant
+from homeassistant.setup import async_setup_component
+from homeassistant.util.json import JsonArrayType, JsonObjectType
+
+from .const import ACCESS_TOKEN, REFRESH_TOKEN
+
+from tests.common import load_json_array_fixture, load_json_object_fixture
+
+TEST_CONFIG: Final = {
+    CONF_USERNAME: "username",
+    CONF_PASSWORD: "password",
+}
+
+
+def user_account_config_fixture() -> JsonObjectType:
+    """Load JSON for the config of a user's account."""
+    return load_json_object_fixture("user_account.json", DOMAIN)
+
+
+def user_locations_config_fixture() -> JsonArrayType:
+    """Load JSON for the config of a user's installation (a list of locations)."""
+    return load_json_array_fixture("user_locations.json", DOMAIN)
+
+
+def location_status_fixture(loc_id: str) -> JsonObjectType:
+    """Load JSON for the status of a specific location."""
+    return load_json_object_fixture(f"status_{loc_id}.json", DOMAIN)
+
+
+def dhw_schedule_fixture() -> JsonObjectType:
+    """Load JSON for the schedule of a domesticHotWater zone."""
+    return load_json_object_fixture("schedule_dhw.json", DOMAIN)
+
+
+def zone_schedule_fixture() -> JsonObjectType:
+    """Load JSON for the schedule of a temperatureZone zone."""
+    return load_json_object_fixture("schedule_zone.json", DOMAIN)
+
+
+async def mock_get(
+    self: Broker, url: str, **kwargs: Any
+) -> JsonArrayType | JsonObjectType:
+    """Return the JSON for a HTTP get of a given URL."""
+
+    # a proxy for the behaviour of the real web API
+    if self.refresh_token is None:
+        self.refresh_token = f"new_{REFRESH_TOKEN}"
+
+    if self.access_token_expires is None or self.access_token_expires < datetime.now():
+        self.access_token = f"new_{ACCESS_TOKEN}"
+        self.access_token_expires = datetime.now() + timedelta(minutes=30)
+
+    # assume a valid GET, and return the JSON for that web API
+    if url == "userAccount":  #                    userAccount
+        return user_account_config_fixture()
+
+    if url.startswith("location"):
+        if "installationInfo" in url:  #           location/installationInfo?userId={id}
+            return user_locations_config_fixture()
+        if "location" in url:  #                   location/{id}/status
+            return location_status_fixture("2738909")
+
+    elif "schedule" in url:
+        if url.startswith("domesticHotWater"):  #  domesticHotWater/{id}/schedule
+            return dhw_schedule_fixture()
+        if url.startswith("temperatureZone"):  #   temperatureZone/{id}/schedule
+            return zone_schedule_fixture()
+
+    pytest.xfail(f"Unexpected URL: {url}")
+
+
+@patch("evohomeasync2.broker.Broker.get", mock_get)
+async def setup_evohome(hass: HomeAssistant, test_config: dict[str, str]) -> MagicMock:
+    """Set up the evohome integration and return its client.
+
+    The class is mocked here to check the client was instantiated with the correct args.
+    """
+
+    with (
+        patch("homeassistant.components.evohome.evo.EvohomeClient") as mock_client,
+        patch("homeassistant.components.evohome.ev1.EvohomeClient", return_value=None),
+    ):
+        mock_client.side_effect = EvohomeClient
+
+        assert await async_setup_component(hass, DOMAIN, {DOMAIN: test_config})
+        await hass.async_block_till_done()
+
+        mock_client.assert_called_once()
+
+        assert mock_client.call_args.args[0] == test_config[CONF_USERNAME]
+        assert mock_client.call_args.args[1] == test_config[CONF_PASSWORD]
+
+        assert isinstance(mock_client.call_args.kwargs["session"], ClientSession)
+
+        assert mock_client.account_info is not None
+
+        return mock_client
diff --git a/tests/components/evohome/const.py b/tests/components/evohome/const.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b298db533ac69eb3050287965528f2725d38dc8
--- /dev/null
+++ b/tests/components/evohome/const.py
@@ -0,0 +1,10 @@
+"""Constants for the evohome tests."""
+
+from __future__ import annotations
+
+from typing import Final
+
+ACCESS_TOKEN: Final = "at_1dc7z657UKzbhKA..."
+REFRESH_TOKEN: Final = "rf_jg68ZCKYdxEI3fF..."
+SESSION_ID: Final = "F7181186..."
+USERNAME: Final = "test_user@gmail.com"
diff --git a/tests/components/evohome/fixtures/schedule_dhw.json b/tests/components/evohome/fixtures/schedule_dhw.json
new file mode 100644
index 0000000000000000000000000000000000000000..da9a225fb8226ee09a832e7653035b2dc72ae876
--- /dev/null
+++ b/tests/components/evohome/fixtures/schedule_dhw.json
@@ -0,0 +1,81 @@
+{
+  "dailySchedules": [
+    {
+      "dayOfWeek": "Monday",
+      "switchpoints": [
+        { "dhwState": "On", "timeOfDay": "06:30:00" },
+        { "dhwState": "Off", "timeOfDay": "08:30:00" },
+        { "dhwState": "On", "timeOfDay": "12:00:00" },
+        { "dhwState": "Off", "timeOfDay": "13:00:00" },
+        { "dhwState": "On", "timeOfDay": "16:30:00" },
+        { "dhwState": "Off", "timeOfDay": "22:30:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Tuesday",
+      "switchpoints": [
+        { "dhwState": "On", "timeOfDay": "06:30:00" },
+        { "dhwState": "Off", "timeOfDay": "08:30:00" },
+        { "dhwState": "On", "timeOfDay": "12:00:00" },
+        { "dhwState": "Off", "timeOfDay": "13:00:00" },
+        { "dhwState": "On", "timeOfDay": "16:30:00" },
+        { "dhwState": "Off", "timeOfDay": "22:30:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Wednesday",
+      "switchpoints": [
+        { "dhwState": "On", "timeOfDay": "06:30:00" },
+        { "dhwState": "Off", "timeOfDay": "08:30:00" },
+        { "dhwState": "On", "timeOfDay": "12:00:00" },
+        { "dhwState": "Off", "timeOfDay": "13:00:00" },
+        { "dhwState": "On", "timeOfDay": "16:30:00" },
+        { "dhwState": "Off", "timeOfDay": "22:30:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Thursday",
+      "switchpoints": [
+        { "dhwState": "On", "timeOfDay": "06:30:00" },
+        { "dhwState": "Off", "timeOfDay": "08:30:00" },
+        { "dhwState": "On", "timeOfDay": "12:00:00" },
+        { "dhwState": "Off", "timeOfDay": "13:00:00" },
+        { "dhwState": "On", "timeOfDay": "16:30:00" },
+        { "dhwState": "Off", "timeOfDay": "22:30:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Friday",
+      "switchpoints": [
+        { "dhwState": "On", "timeOfDay": "06:30:00" },
+        { "dhwState": "Off", "timeOfDay": "08:30:00" },
+        { "dhwState": "On", "timeOfDay": "12:00:00" },
+        { "dhwState": "Off", "timeOfDay": "13:00:00" },
+        { "dhwState": "On", "timeOfDay": "16:30:00" },
+        { "dhwState": "Off", "timeOfDay": "22:30:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Saturday",
+      "switchpoints": [
+        { "dhwState": "On", "timeOfDay": "06:30:00" },
+        { "dhwState": "Off", "timeOfDay": "09:30:00" },
+        { "dhwState": "On", "timeOfDay": "12:00:00" },
+        { "dhwState": "Off", "timeOfDay": "13:00:00" },
+        { "dhwState": "On", "timeOfDay": "16:30:00" },
+        { "dhwState": "Off", "timeOfDay": "23:00:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Sunday",
+      "switchpoints": [
+        { "dhwState": "On", "timeOfDay": "06:30:00" },
+        { "dhwState": "Off", "timeOfDay": "09:30:00" },
+        { "dhwState": "On", "timeOfDay": "12:00:00" },
+        { "dhwState": "Off", "timeOfDay": "13:00:00" },
+        { "dhwState": "On", "timeOfDay": "16:30:00" },
+        { "dhwState": "Off", "timeOfDay": "23:00:00" }
+      ]
+    }
+  ]
+}
diff --git a/tests/components/evohome/fixtures/schedule_zone.json b/tests/components/evohome/fixtures/schedule_zone.json
new file mode 100644
index 0000000000000000000000000000000000000000..5030d92ff3ddadfba1b08b29ecf6f884f3adc497
--- /dev/null
+++ b/tests/components/evohome/fixtures/schedule_zone.json
@@ -0,0 +1,67 @@
+{
+  "dailySchedules": [
+    {
+      "dayOfWeek": "Monday",
+      "switchpoints": [
+        { "heatSetpoint": 18.1, "timeOfDay": "07:00:00" },
+        { "heatSetpoint": 16.0, "timeOfDay": "08:00:00" },
+        { "heatSetpoint": 18.6, "timeOfDay": "22:10:00" },
+        { "heatSetpoint": 15.9, "timeOfDay": "23:00:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Tuesday",
+      "switchpoints": [
+        { "heatSetpoint": 18.1, "timeOfDay": "07:00:00" },
+        { "heatSetpoint": 16.0, "timeOfDay": "08:00:00" },
+        { "heatSetpoint": 18.6, "timeOfDay": "22:10:00" },
+        { "heatSetpoint": 15.9, "timeOfDay": "23:00:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Wednesday",
+      "switchpoints": [
+        { "heatSetpoint": 18.1, "timeOfDay": "07:00:00" },
+        { "heatSetpoint": 16.0, "timeOfDay": "08:00:00" },
+        { "heatSetpoint": 18.6, "timeOfDay": "22:10:00" },
+        { "heatSetpoint": 15.9, "timeOfDay": "23:00:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Thursday",
+      "switchpoints": [
+        { "heatSetpoint": 18.1, "timeOfDay": "07:00:00" },
+        { "heatSetpoint": 16.0, "timeOfDay": "08:00:00" },
+        { "heatSetpoint": 18.6, "timeOfDay": "22:10:00" },
+        { "heatSetpoint": 15.9, "timeOfDay": "23:00:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Friday",
+      "switchpoints": [
+        { "heatSetpoint": 18.1, "timeOfDay": "07:00:00" },
+        { "heatSetpoint": 16.0, "timeOfDay": "08:00:00" },
+        { "heatSetpoint": 18.6, "timeOfDay": "22:10:00" },
+        { "heatSetpoint": 15.9, "timeOfDay": "23:00:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Saturday",
+      "switchpoints": [
+        { "heatSetpoint": 18.5, "timeOfDay": "07:00:00" },
+        { "heatSetpoint": 16.0, "timeOfDay": "08:30:00" },
+        { "heatSetpoint": 18.6, "timeOfDay": "22:10:00" },
+        { "heatSetpoint": 15.9, "timeOfDay": "23:00:00" }
+      ]
+    },
+    {
+      "dayOfWeek": "Sunday",
+      "switchpoints": [
+        { "heatSetpoint": 18.5, "timeOfDay": "07:00:00" },
+        { "heatSetpoint": 16.0, "timeOfDay": "08:30:00" },
+        { "heatSetpoint": 18.6, "timeOfDay": "22:10:00" },
+        { "heatSetpoint": 15.9, "timeOfDay": "23:00:00" }
+      ]
+    }
+  ]
+}
diff --git a/tests/components/evohome/fixtures/status_2738909.json b/tests/components/evohome/fixtures/status_2738909.json
new file mode 100644
index 0000000000000000000000000000000000000000..6d555ba4e3e49c5540e3287a2be3e09a00306bbb
--- /dev/null
+++ b/tests/components/evohome/fixtures/status_2738909.json
@@ -0,0 +1,125 @@
+{
+  "locationId": "2738909",
+  "gateways": [
+    {
+      "gatewayId": "2499896",
+      "temperatureControlSystems": [
+        {
+          "systemId": "3432522",
+          "zones": [
+            {
+              "zoneId": "3432521",
+              "name": "Dead Zone",
+              "temperatureStatus": { "isAvailable": false },
+              "setpointStatus": {
+                "targetHeatTemperature": 17.0,
+                "setpointMode": "FollowSchedule"
+              },
+              "activeFaults": []
+            },
+            {
+              "zoneId": "3432576",
+              "name": "Main Room",
+              "temperatureStatus": { "temperature": 19.0, "isAvailable": true },
+              "setpointStatus": {
+                "targetHeatTemperature": 17.0,
+                "setpointMode": "PermanentOverride"
+              },
+              "activeFaults": [
+                {
+                  "faultType": "TempZoneActuatorCommunicationLost",
+                  "since": "2022-03-02T15:56:01"
+                }
+              ]
+            },
+            {
+              "zoneId": "3432577",
+              "name": "Front Room",
+              "temperatureStatus": { "temperature": 19.0, "isAvailable": true },
+              "setpointStatus": {
+                "targetHeatTemperature": 21.0,
+                "setpointMode": "TemporaryOverride",
+                "until": "2022-03-07T19:00:00Z"
+              },
+              "activeFaults": [
+                {
+                  "faultType": "TempZoneActuatorLowBattery",
+                  "since": "2022-03-02T04:50:20"
+                }
+              ]
+            },
+            {
+              "zoneId": "3432578",
+              "temperatureStatus": { "temperature": 20.0, "isAvailable": true },
+              "activeFaults": [],
+              "setpointStatus": {
+                "targetHeatTemperature": 17.0,
+                "setpointMode": "FollowSchedule"
+              },
+              "name": "Kitchen"
+            },
+            {
+              "zoneId": "3432579",
+              "temperatureStatus": { "temperature": 20.0, "isAvailable": true },
+              "activeFaults": [],
+              "setpointStatus": {
+                "targetHeatTemperature": 16.0,
+                "setpointMode": "FollowSchedule"
+              },
+              "name": "Bathroom Dn"
+            },
+            {
+              "zoneId": "3432580",
+              "temperatureStatus": { "temperature": 21.0, "isAvailable": true },
+              "activeFaults": [],
+              "setpointStatus": {
+                "targetHeatTemperature": 16.0,
+                "setpointMode": "FollowSchedule"
+              },
+              "name": "Main Bedroom"
+            },
+            {
+              "zoneId": "3449703",
+              "temperatureStatus": { "temperature": 19.5, "isAvailable": true },
+              "activeFaults": [],
+              "setpointStatus": {
+                "targetHeatTemperature": 17.0,
+                "setpointMode": "FollowSchedule"
+              },
+              "name": "Kids Room"
+            },
+            {
+              "zoneId": "3449740",
+              "temperatureStatus": { "temperature": 21.5, "isAvailable": true },
+              "activeFaults": [],
+              "setpointStatus": {
+                "targetHeatTemperature": 16.5,
+                "setpointMode": "FollowSchedule"
+              },
+              "name": ""
+            },
+            {
+              "zoneId": "3450733",
+              "temperatureStatus": { "temperature": 19.5, "isAvailable": true },
+              "activeFaults": [],
+              "setpointStatus": {
+                "targetHeatTemperature": 14.0,
+                "setpointMode": "PermanentOverride"
+              },
+              "name": "Spare Room"
+            }
+          ],
+          "dhw": {
+            "dhwId": "3933910",
+            "temperatureStatus": { "temperature": 23.0, "isAvailable": true },
+            "stateStatus": { "state": "Off", "mode": "PermanentOverride" },
+            "activeFaults": []
+          },
+          "activeFaults": [],
+          "systemModeStatus": { "mode": "AutoWithEco", "isPermanent": true }
+        }
+      ],
+      "activeFaults": []
+    }
+  ]
+}
diff --git a/tests/components/evohome/fixtures/user_account.json b/tests/components/evohome/fixtures/user_account.json
new file mode 100644
index 0000000000000000000000000000000000000000..99a96a7961e314a787621a674f5cfcc673c5e1cc
--- /dev/null
+++ b/tests/components/evohome/fixtures/user_account.json
@@ -0,0 +1,11 @@
+{
+  "userId": "2263181",
+  "username": "user_2263181@gmail.com",
+  "firstname": "John",
+  "lastname": "Smith",
+  "streetAddress": "1 Main Street",
+  "city": "London",
+  "postcode": "E1 1AA",
+  "country": "UnitedKingdom",
+  "language": "enGB"
+}
diff --git a/tests/components/evohome/fixtures/user_locations.json b/tests/components/evohome/fixtures/user_locations.json
new file mode 100644
index 0000000000000000000000000000000000000000..cf59aa9ae8a827f688869abbf01c512e76fc2d1b
--- /dev/null
+++ b/tests/components/evohome/fixtures/user_locations.json
@@ -0,0 +1,346 @@
+[
+  {
+    "locationInfo": {
+      "locationId": "2738909",
+      "name": "My Home",
+      "streetAddress": "1 Main Street",
+      "city": "London",
+      "country": "UnitedKingdom",
+      "postcode": "E1 1AA",
+      "locationType": "Residential",
+      "useDaylightSaveSwitching": true,
+      "timeZone": {
+        "timeZoneId": "GMTStandardTime",
+        "displayName": "(UTC+00:00) Dublin, Edinburgh, Lisbon, London",
+        "offsetMinutes": 0,
+        "currentOffsetMinutes": 60,
+        "supportsDaylightSaving": true
+      },
+      "locationOwner": {
+        "userId": "2263181",
+        "username": "user_2263181@gmail.com",
+        "firstname": "John",
+        "lastname": "Smith"
+      }
+    },
+    "gateways": [
+      {
+        "gatewayInfo": {
+          "gatewayId": "2499896",
+          "mac": "00D02DEE0000",
+          "crc": "1234",
+          "isWiFi": false
+        },
+        "temperatureControlSystems": [
+          {
+            "systemId": "3432522",
+            "modelType": "EvoTouch",
+            "zones": [
+              {
+                "zoneId": "3432521",
+                "modelType": "HeatingZone",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "Dead Zone",
+                "zoneType": "RadiatorZone"
+              },
+              {
+                "zoneId": "3432576",
+                "modelType": "HeatingZone",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "Main Room",
+                "zoneType": "RadiatorZone"
+              },
+              {
+                "zoneId": "3432577",
+                "modelType": "HeatingZone",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "Front Room",
+                "zoneType": "RadiatorZone"
+              },
+              {
+                "zoneId": "3432578",
+                "modelType": "HeatingZone",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "Kitchen",
+                "zoneType": "RadiatorZone"
+              },
+              {
+                "zoneId": "3432579",
+                "modelType": "HeatingZone",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "Bathroom Dn",
+                "zoneType": "RadiatorZone"
+              },
+              {
+                "zoneId": "3432580",
+                "modelType": "HeatingZone",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "Main Bedroom",
+                "zoneType": "RadiatorZone"
+              },
+              {
+                "zoneId": "3449703",
+                "modelType": "HeatingZone",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "Kids Room",
+                "zoneType": "RadiatorZone"
+              },
+              {
+                "zoneId": "3449740",
+                "modelType": "Unknown",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "",
+                "zoneType": "Unknown"
+              },
+              {
+                "zoneId": "3450733",
+                "modelType": "xx",
+                "setpointCapabilities": {
+                  "maxHeatSetpoint": 35.0,
+                  "minHeatSetpoint": 5.0,
+                  "valueResolution": 0.5,
+                  "canControlHeat": true,
+                  "canControlCool": false,
+                  "allowedSetpointModes": [
+                    "PermanentOverride",
+                    "FollowSchedule",
+                    "TemporaryOverride"
+                  ],
+                  "maxDuration": "1.00:00:00",
+                  "timingResolution": "00:10:00"
+                },
+                "scheduleCapabilities": {
+                  "maxSwitchpointsPerDay": 6,
+                  "minSwitchpointsPerDay": 1,
+                  "timingResolution": "00:10:00",
+                  "setpointValueResolution": 0.5
+                },
+                "name": "Spare Room",
+                "zoneType": "xx"
+              }
+            ],
+            "dhw": {
+              "dhwId": "3933910",
+              "dhwStateCapabilitiesResponse": {
+                "allowedStates": ["On", "Off"],
+                "allowedModes": [
+                  "FollowSchedule",
+                  "PermanentOverride",
+                  "TemporaryOverride"
+                ],
+                "maxDuration": "1.00:00:00",
+                "timingResolution": "00:10:00"
+              },
+              "scheduleCapabilitiesResponse": {
+                "maxSwitchpointsPerDay": 6,
+                "minSwitchpointsPerDay": 1,
+                "timingResolution": "00:10:00"
+              }
+            },
+            "allowedSystemModes": [
+              {
+                "systemMode": "HeatingOff",
+                "canBePermanent": true,
+                "canBeTemporary": false
+              },
+              {
+                "systemMode": "Auto",
+                "canBePermanent": true,
+                "canBeTemporary": false
+              },
+              {
+                "systemMode": "AutoWithReset",
+                "canBePermanent": true,
+                "canBeTemporary": false
+              },
+              {
+                "systemMode": "AutoWithEco",
+                "canBePermanent": true,
+                "canBeTemporary": true,
+                "maxDuration": "1.00:00:00",
+                "timingResolution": "01:00:00",
+                "timingMode": "Duration"
+              },
+              {
+                "systemMode": "Away",
+                "canBePermanent": true,
+                "canBeTemporary": true,
+                "maxDuration": "99.00:00:00",
+                "timingResolution": "1.00:00:00",
+                "timingMode": "Period"
+              },
+              {
+                "systemMode": "DayOff",
+                "canBePermanent": true,
+                "canBeTemporary": true,
+                "maxDuration": "99.00:00:00",
+                "timingResolution": "1.00:00:00",
+                "timingMode": "Period"
+              },
+              {
+                "systemMode": "Custom",
+                "canBePermanent": true,
+                "canBeTemporary": true,
+                "maxDuration": "99.00:00:00",
+                "timingResolution": "1.00:00:00",
+                "timingMode": "Period"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]
diff --git a/tests/components/evohome/test_storage.py b/tests/components/evohome/test_storage.py
new file mode 100644
index 0000000000000000000000000000000000000000..e87b847a9ff5013c85d4a5e15d76b728815215c3
--- /dev/null
+++ b/tests/components/evohome/test_storage.py
@@ -0,0 +1,208 @@
+"""The tests for evohome storage load & save."""
+
+from __future__ import annotations
+
+from datetime import datetime, timedelta
+from typing import Any, Final, NotRequired, TypedDict
+
+import pytest
+
+from homeassistant.components.evohome import (
+    CONF_PASSWORD,
+    CONF_USERNAME,
+    DOMAIN,
+    STORAGE_KEY,
+    STORAGE_VER,
+    dt_aware_to_naive,
+)
+from homeassistant.core import HomeAssistant
+import homeassistant.util.dt as dt_util
+
+from .conftest import setup_evohome
+from .const import ACCESS_TOKEN, REFRESH_TOKEN, SESSION_ID, USERNAME
+
+
+class _SessionDataT(TypedDict):
+    sessionId: str
+
+
+class _TokenStoreT(TypedDict):
+    username: str
+    refresh_token: str
+    access_token: str
+    access_token_expires: str  # 2024-07-27T23:57:30+01:00
+    user_data: NotRequired[_SessionDataT]
+
+
+class _EmptyStoreT(TypedDict):
+    pass
+
+
+SZ_USERNAME: Final = "username"
+SZ_REFRESH_TOKEN: Final = "refresh_token"
+SZ_ACCESS_TOKEN: Final = "access_token"
+SZ_ACCESS_TOKEN_EXPIRES: Final = "access_token_expires"
+SZ_USER_DATA: Final = "user_data"
+
+
+def dt_pair(dt_dtm: datetime) -> tuple[datetime, str]:
+    """Return a datetime without milliseconds and its string representation."""
+    dt_str = dt_dtm.isoformat(timespec="seconds")  # e.g. 2024-07-28T00:57:29+01:00
+    return dt_util.parse_datetime(dt_str, raise_on_error=True), dt_str
+
+
+ACCESS_TOKEN_EXP_DTM, ACCESS_TOKEN_EXP_STR = dt_pair(dt_util.now() + timedelta(hours=1))
+
+USERNAME_DIFF: Final = f"not_{USERNAME}"
+USERNAME_SAME: Final = USERNAME
+
+TEST_CONFIG: Final = {
+    CONF_USERNAME: USERNAME_SAME,
+    CONF_PASSWORD: "password",
+}
+
+TEST_DATA: Final[dict[str, _TokenStoreT]] = {
+    "sans_session_id": {
+        SZ_USERNAME: USERNAME_SAME,
+        SZ_REFRESH_TOKEN: REFRESH_TOKEN,
+        SZ_ACCESS_TOKEN: ACCESS_TOKEN,
+        SZ_ACCESS_TOKEN_EXPIRES: ACCESS_TOKEN_EXP_STR,
+    },
+    "with_session_id": {
+        SZ_USERNAME: USERNAME_SAME,
+        SZ_REFRESH_TOKEN: REFRESH_TOKEN,
+        SZ_ACCESS_TOKEN: ACCESS_TOKEN,
+        SZ_ACCESS_TOKEN_EXPIRES: ACCESS_TOKEN_EXP_STR,
+        SZ_USER_DATA: {"sessionId": SESSION_ID},
+    },
+}
+
+TEST_DATA_NULL: Final[dict[str, _EmptyStoreT | None]] = {
+    "store_is_absent": None,
+    "store_was_reset": {},
+}
+
+DOMAIN_STORAGE_BASE: Final = {
+    "version": STORAGE_VER,
+    "minor_version": 1,
+    "key": STORAGE_KEY,
+}
+
+
+@pytest.mark.parametrize("idx", TEST_DATA_NULL)
+async def test_auth_tokens_null(
+    hass: HomeAssistant,
+    hass_storage: dict[str, Any],
+    idx: str,
+) -> None:
+    """Test loading/saving authentication tokens when no cached tokens in the store."""
+
+    hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_DATA_NULL[idx]}
+
+    mock_client = await setup_evohome(hass, TEST_CONFIG)
+
+    # Confirm client was instantiated without tokens, as cache was empty...
+    assert SZ_REFRESH_TOKEN not in mock_client.call_args.kwargs
+    assert SZ_ACCESS_TOKEN not in mock_client.call_args.kwargs
+    assert SZ_ACCESS_TOKEN_EXPIRES not in mock_client.call_args.kwarg
+
+    # Confirm the expected tokens were cached to storage...
+    data: _TokenStoreT = hass_storage[DOMAIN]["data"]
+
+    assert data[SZ_USERNAME] == USERNAME_SAME
+    assert data[SZ_REFRESH_TOKEN] == f"new_{REFRESH_TOKEN}"
+    assert data[SZ_ACCESS_TOKEN] == f"new_{ACCESS_TOKEN}"
+    assert (
+        dt_util.parse_datetime(data[SZ_ACCESS_TOKEN_EXPIRES], raise_on_error=True)
+        > dt_util.now()
+    )
+
+
+@pytest.mark.parametrize("idx", TEST_DATA)
+async def test_auth_tokens_same(
+    hass: HomeAssistant, hass_storage: dict[str, Any], idx: str
+) -> None:
+    """Test loading/saving authentication tokens when matching username."""
+
+    hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_DATA[idx]}
+
+    mock_client = await setup_evohome(hass, TEST_CONFIG)
+
+    # Confirm client was instantiated with the cached tokens...
+    assert mock_client.call_args.kwargs[SZ_REFRESH_TOKEN] == REFRESH_TOKEN
+    assert mock_client.call_args.kwargs[SZ_ACCESS_TOKEN] == ACCESS_TOKEN
+    assert mock_client.call_args.kwargs[SZ_ACCESS_TOKEN_EXPIRES] == dt_aware_to_naive(
+        ACCESS_TOKEN_EXP_DTM
+    )
+
+    # Confirm the expected tokens were cached to storage...
+    data: _TokenStoreT = hass_storage[DOMAIN]["data"]
+
+    assert data[SZ_USERNAME] == USERNAME_SAME
+    assert data[SZ_REFRESH_TOKEN] == REFRESH_TOKEN
+    assert data[SZ_ACCESS_TOKEN] == ACCESS_TOKEN
+    assert dt_util.parse_datetime(data[SZ_ACCESS_TOKEN_EXPIRES]) == ACCESS_TOKEN_EXP_DTM
+
+
+@pytest.mark.parametrize("idx", TEST_DATA)
+async def test_auth_tokens_past(
+    hass: HomeAssistant, hass_storage: dict[str, Any], idx: str
+) -> None:
+    """Test loading/saving authentication tokens with matching username, but expired."""
+
+    dt_dtm, dt_str = dt_pair(dt_util.now() - timedelta(hours=1))
+
+    # make this access token have expired in the past...
+    test_data = TEST_DATA[idx].copy()  # shallow copy is OK here
+    test_data[SZ_ACCESS_TOKEN_EXPIRES] = dt_str
+
+    hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": test_data}
+
+    mock_client = await setup_evohome(hass, TEST_CONFIG)
+
+    # Confirm client was instantiated with the cached tokens...
+    assert mock_client.call_args.kwargs[SZ_REFRESH_TOKEN] == REFRESH_TOKEN
+    assert mock_client.call_args.kwargs[SZ_ACCESS_TOKEN] == ACCESS_TOKEN
+    assert mock_client.call_args.kwargs[SZ_ACCESS_TOKEN_EXPIRES] == dt_aware_to_naive(
+        dt_dtm
+    )
+
+    # Confirm the expected tokens were cached to storage...
+    data: _TokenStoreT = hass_storage[DOMAIN]["data"]
+
+    assert data[SZ_USERNAME] == USERNAME_SAME
+    assert data[SZ_REFRESH_TOKEN] == REFRESH_TOKEN
+    assert data[SZ_ACCESS_TOKEN] == f"new_{ACCESS_TOKEN}"
+    assert (
+        dt_util.parse_datetime(data[SZ_ACCESS_TOKEN_EXPIRES], raise_on_error=True)
+        > dt_util.now()
+    )
+
+
+@pytest.mark.parametrize("idx", TEST_DATA)
+async def test_auth_tokens_diff(
+    hass: HomeAssistant, hass_storage: dict[str, Any], idx: str
+) -> None:
+    """Test loading/saving authentication tokens when unmatched username."""
+
+    hass_storage[DOMAIN] = DOMAIN_STORAGE_BASE | {"data": TEST_DATA[idx]}
+
+    mock_client = await setup_evohome(
+        hass, TEST_CONFIG | {CONF_USERNAME: USERNAME_DIFF}
+    )
+
+    # Confirm client was instantiated without tokens, as username was different...
+    assert SZ_REFRESH_TOKEN not in mock_client.call_args.kwargs
+    assert SZ_ACCESS_TOKEN not in mock_client.call_args.kwargs
+    assert SZ_ACCESS_TOKEN_EXPIRES not in mock_client.call_args.kwarg
+
+    # Confirm the expected tokens were cached to storage...
+    data: _TokenStoreT = hass_storage[DOMAIN]["data"]
+
+    assert data[SZ_USERNAME] == USERNAME_DIFF
+    assert data[SZ_REFRESH_TOKEN] == f"new_{REFRESH_TOKEN}"
+    assert data[SZ_ACCESS_TOKEN] == f"new_{ACCESS_TOKEN}"
+    assert (
+        dt_util.parse_datetime(data[SZ_ACCESS_TOKEN_EXPIRES], raise_on_error=True)
+        > dt_util.now()
+    )