diff --git a/.coveragerc b/.coveragerc
index 28a6eff3073b277657c4aead678ad9dd41dcb686..53012078f331d0b24c33636df7a82728e15a478f 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -399,8 +399,6 @@ omit =
     homeassistant/components/google_travel_time/sensor.py
     homeassistant/components/gpmdp/media_player.py
     homeassistant/components/gpsd/sensor.py
-    homeassistant/components/greeneye_monitor/*
-    homeassistant/components/greeneye_monitor/sensor.py
     homeassistant/components/greenwave/light.py
     homeassistant/components/group/notify.py
     homeassistant/components/growatt_server/sensor.py
diff --git a/homeassistant/components/greeneye_monitor/__init__.py b/homeassistant/components/greeneye_monitor/__init__.py
index 3417d0c08dc4d338cfc42b271d5672f5ed952dce..d2b0e7c307ba64de78f2f8ff1c64d049649919bd 100644
--- a/homeassistant/components/greeneye_monitor/__init__.py
+++ b/homeassistant/components/greeneye_monitor/__init__.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 
 import logging
 
-from greeneye import Monitors
+import greeneye
 import voluptuous as vol
 
 from homeassistant.const import (
@@ -123,7 +123,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: COMPONENT_SCHEMA}, extra=vol.ALLOW_EXTRA)
 
 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
     """Set up the GreenEye Monitor component."""
-    monitors = Monitors()
+    monitors = greeneye.Monitors()
     hass.data[DATA_GREENEYE_MONITOR] = monitors
 
     server_config = config[DOMAIN]
diff --git a/homeassistant/components/greeneye_monitor/sensor.py b/homeassistant/components/greeneye_monitor/sensor.py
index 2a4692674c9e9d3341562c1dc0dafc573bd29fd2..de71e3c27fab047ca9527eff35e471453858da46 100644
--- a/homeassistant/components/greeneye_monitor/sensor.py
+++ b/homeassistant/components/greeneye_monitor/sensor.py
@@ -4,7 +4,6 @@ from __future__ import annotations
 from typing import Any, Generic, Optional, TypeVar, cast
 
 import greeneye
-from greeneye import Monitors
 
 from homeassistant.components.sensor import SensorEntity
 from homeassistant.const import (
@@ -145,7 +144,7 @@ class GEMSensor(Generic[T], SensorEntity):
             monitors = self.hass.data[DATA_GREENEYE_MONITOR]
             monitors.remove_listener(self._on_new_monitor)
 
-    def _try_connect_to_monitor(self, monitors: Monitors) -> bool:
+    def _try_connect_to_monitor(self, monitors: greeneye.Monitors) -> bool:
         monitor = monitors.monitors.get(self._monitor_serial_number)
         if not monitor:
             return False
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 4e25d14b60f4fcddd202b5e8690e1ec3c23ba2de..77ab56dc4849ee966902f3e77c66c1ec26de33ff 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -463,6 +463,9 @@ googlemaps==2.5.1
 # homeassistant.components.gree
 greeclimate==0.12.3
 
+# homeassistant.components.greeneye_monitor
+greeneye_monitor==2.1
+
 # homeassistant.components.growatt_server
 growattServer==1.1.0
 
diff --git a/tests/components/greeneye_monitor/__init__.py b/tests/components/greeneye_monitor/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..db9bcaee1f45a0be83bf18a9bbc90f8aba9a9b2f
--- /dev/null
+++ b/tests/components/greeneye_monitor/__init__.py
@@ -0,0 +1 @@
+"""Tests for the GreenEye Monitor integration."""
diff --git a/tests/components/greeneye_monitor/common.py b/tests/components/greeneye_monitor/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..ac00ccbfc0b27fc344a485180a26a6021adcf70c
--- /dev/null
+++ b/tests/components/greeneye_monitor/common.py
@@ -0,0 +1,205 @@
+"""Common helpers for greeneye_monitor tests."""
+from __future__ import annotations
+
+from typing import Any
+from unittest.mock import AsyncMock, MagicMock
+
+from homeassistant.components.greeneye_monitor import (
+    CONF_CHANNELS,
+    CONF_COUNTED_QUANTITY,
+    CONF_COUNTED_QUANTITY_PER_PULSE,
+    CONF_MONITORS,
+    CONF_NET_METERING,
+    CONF_NUMBER,
+    CONF_PULSE_COUNTERS,
+    CONF_SERIAL_NUMBER,
+    CONF_TEMPERATURE_SENSORS,
+    CONF_TIME_UNIT,
+    CONF_VOLTAGE_SENSORS,
+    DOMAIN,
+)
+from homeassistant.const import (
+    CONF_NAME,
+    CONF_PORT,
+    CONF_SENSORS,
+    CONF_TEMPERATURE_UNIT,
+)
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.typing import ConfigType
+from homeassistant.setup import async_setup_component
+
+SINGLE_MONITOR_SERIAL_NUMBER = 110011
+
+
+def make_single_monitor_config_with_sensors(sensors: dict[str, Any]) -> dict[str, Any]:
+    """Wrap the given sensor config in the boilerplate for a single monitor with serial number SINGLE_MONITOR_SERIAL_NUMBER."""
+    return {
+        DOMAIN: {
+            CONF_PORT: 7513,
+            CONF_MONITORS: [
+                {
+                    CONF_SERIAL_NUMBER: f"00{SINGLE_MONITOR_SERIAL_NUMBER}",
+                    **sensors,
+                }
+            ],
+        }
+    }
+
+
+SINGLE_MONITOR_CONFIG_NO_SENSORS = make_single_monitor_config_with_sensors({})
+SINGLE_MONITOR_CONFIG_PULSE_COUNTERS = make_single_monitor_config_with_sensors(
+    {
+        CONF_PULSE_COUNTERS: [
+            {
+                CONF_NUMBER: 1,
+                CONF_NAME: "pulse_a",
+                CONF_COUNTED_QUANTITY: "pulses",
+                CONF_COUNTED_QUANTITY_PER_PULSE: 1.0,
+                CONF_TIME_UNIT: "s",
+            },
+            {
+                CONF_NUMBER: 2,
+                CONF_NAME: "pulse_2",
+                CONF_COUNTED_QUANTITY: "gal",
+                CONF_COUNTED_QUANTITY_PER_PULSE: 0.5,
+                CONF_TIME_UNIT: "min",
+            },
+            {
+                CONF_NUMBER: 3,
+                CONF_NAME: "pulse_3",
+                CONF_COUNTED_QUANTITY: "gal",
+                CONF_COUNTED_QUANTITY_PER_PULSE: 0.5,
+                CONF_TIME_UNIT: "h",
+            },
+            {
+                CONF_NUMBER: 4,
+                CONF_NAME: "pulse_d",
+                CONF_COUNTED_QUANTITY: "pulses",
+                CONF_COUNTED_QUANTITY_PER_PULSE: 1.0,
+                CONF_TIME_UNIT: "s",
+            },
+        ]
+    }
+)
+
+SINGLE_MONITOR_CONFIG_POWER_SENSORS = make_single_monitor_config_with_sensors(
+    {
+        CONF_CHANNELS: [
+            {
+                CONF_NUMBER: 1,
+                CONF_NAME: "channel 1",
+            },
+            {
+                CONF_NUMBER: 2,
+                CONF_NAME: "channel two",
+                CONF_NET_METERING: True,
+            },
+        ]
+    }
+)
+
+
+SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS = make_single_monitor_config_with_sensors(
+    {
+        CONF_TEMPERATURE_SENSORS: {
+            CONF_TEMPERATURE_UNIT: "F",
+            CONF_SENSORS: [
+                {CONF_NUMBER: 1, CONF_NAME: "temp_a"},
+                {CONF_NUMBER: 2, CONF_NAME: "temp_2"},
+                {CONF_NUMBER: 3, CONF_NAME: "temp_c"},
+                {CONF_NUMBER: 4, CONF_NAME: "temp_d"},
+                {CONF_NUMBER: 5, CONF_NAME: "temp_5"},
+                {CONF_NUMBER: 6, CONF_NAME: "temp_f"},
+                {CONF_NUMBER: 7, CONF_NAME: "temp_g"},
+                {CONF_NUMBER: 8, CONF_NAME: "temp_h"},
+            ],
+        }
+    }
+)
+
+SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS = make_single_monitor_config_with_sensors(
+    {
+        CONF_VOLTAGE_SENSORS: [
+            {
+                CONF_NUMBER: 1,
+                CONF_NAME: "voltage 1",
+            },
+        ]
+    }
+)
+
+
+async def setup_greeneye_monitor_component_with_config(
+    hass: HomeAssistant, config: ConfigType
+) -> bool:
+    """Set up the greeneye_monitor component with the given config. Return True if successful, False otherwise."""
+    result = await async_setup_component(
+        hass,
+        DOMAIN,
+        config,
+    )
+    await hass.async_block_till_done()
+
+    return result
+
+
+def mock_with_listeners() -> MagicMock:
+    """Create a MagicMock with methods that follow the same pattern for working with listeners in the greeneye_monitor API."""
+    mock = MagicMock()
+    add_listeners(mock)
+    return mock
+
+
+def async_mock_with_listeners() -> AsyncMock:
+    """Create an AsyncMock with methods that follow the same pattern for working with listeners in the greeneye_monitor API."""
+    mock = AsyncMock()
+    add_listeners(mock)
+    return mock
+
+
+def add_listeners(mock: MagicMock | AsyncMock) -> None:
+    """Add add_listener and remove_listener methods to the given mock that behave like their counterparts on objects from the greeneye_monitor API, plus a notify_all_listeners method that calls all registered listeners."""
+    mock.listeners = []
+    mock.add_listener = mock.listeners.append
+    mock.remove_listener = mock.listeners.remove
+
+    def notify_all_listeners(*args):
+        for listener in list(mock.listeners):
+            listener(*args)
+
+    mock.notify_all_listeners = notify_all_listeners
+
+
+def mock_pulse_counter() -> MagicMock:
+    """Create a mock GreenEye Monitor pulse counter."""
+    pulse_counter = mock_with_listeners()
+    pulse_counter.pulses = 1000
+    pulse_counter.pulses_per_second = 10
+    return pulse_counter
+
+
+def mock_temperature_sensor() -> MagicMock:
+    """Create a mock GreenEye Monitor temperature sensor."""
+    temperature_sensor = mock_with_listeners()
+    temperature_sensor.temperature = 32.0
+    return temperature_sensor
+
+
+def mock_channel() -> MagicMock:
+    """Create a mock GreenEye Monitor CT channel."""
+    channel = mock_with_listeners()
+    channel.absolute_watt_seconds = 1000
+    channel.polarized_watt_seconds = -400
+    channel.watts = None
+    return channel
+
+
+def mock_monitor(serial_number: int) -> MagicMock:
+    """Create a mock GreenEye Monitor."""
+    monitor = mock_with_listeners()
+    monitor.serial_number = serial_number
+    monitor.voltage = 120.0
+    monitor.pulse_counters = [mock_pulse_counter() for i in range(0, 4)]
+    monitor.temperature_sensors = [mock_temperature_sensor() for i in range(0, 8)]
+    monitor.channels = [mock_channel() for i in range(0, 32)]
+    return monitor
diff --git a/tests/components/greeneye_monitor/conftest.py b/tests/components/greeneye_monitor/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6fa49032cc618e4e7af24dec0fadc5519ca8cb9
--- /dev/null
+++ b/tests/components/greeneye_monitor/conftest.py
@@ -0,0 +1,118 @@
+"""Common fixtures for testing greeneye_monitor."""
+from typing import Any, Dict
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import pytest
+
+from homeassistant.components.greeneye_monitor import DOMAIN
+from homeassistant.const import (
+    DEVICE_CLASS_POWER,
+    DEVICE_CLASS_TEMPERATURE,
+    DEVICE_CLASS_VOLTAGE,
+    ELECTRIC_POTENTIAL_VOLT,
+    POWER_WATT,
+)
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity_registry import (
+    RegistryEntry,
+    async_get as get_entity_registry,
+)
+
+from .common import add_listeners
+
+
+def assert_sensor_state(
+    hass: HomeAssistant,
+    entity_id: str,
+    expected_state: str,
+    attributes: Dict[str, Any] = {},
+) -> None:
+    """Assert that the given entity has the expected state and at least the provided attributes."""
+    state = hass.states.get(entity_id)
+    assert state
+    actual_state = state.state
+    assert actual_state == expected_state
+    for (key, value) in attributes.items():
+        assert key in state.attributes
+        assert state.attributes[key] == value
+
+
+def assert_temperature_sensor_registered(
+    hass: HomeAssistant,
+    serial_number: int,
+    number: int,
+    name: str,
+):
+    """Assert that a temperature sensor entity was registered properly."""
+    sensor = assert_sensor_registered(hass, serial_number, "temp", number, name)
+    assert sensor.device_class == DEVICE_CLASS_TEMPERATURE
+
+
+def assert_pulse_counter_registered(
+    hass: HomeAssistant,
+    serial_number: int,
+    number: int,
+    name: str,
+    quantity: str,
+    per_time: str,
+):
+    """Assert that a pulse counter entity was registered properly."""
+    sensor = assert_sensor_registered(hass, serial_number, "pulse", number, name)
+    assert sensor.unit_of_measurement == f"{quantity}/{per_time}"
+
+
+def assert_power_sensor_registered(
+    hass: HomeAssistant, serial_number: int, number: int, name: str
+) -> None:
+    """Assert that a power sensor entity was registered properly."""
+    sensor = assert_sensor_registered(hass, serial_number, "current", number, name)
+    assert sensor.unit_of_measurement == POWER_WATT
+    assert sensor.device_class == DEVICE_CLASS_POWER
+
+
+def assert_voltage_sensor_registered(
+    hass: HomeAssistant, serial_number: int, number: int, name: str
+) -> None:
+    """Assert that a voltage sensor entity was registered properly."""
+    sensor = assert_sensor_registered(hass, serial_number, "volts", number, name)
+    assert sensor.unit_of_measurement == ELECTRIC_POTENTIAL_VOLT
+    assert sensor.device_class == DEVICE_CLASS_VOLTAGE
+
+
+def assert_sensor_registered(
+    hass: HomeAssistant,
+    serial_number: int,
+    sensor_type: str,
+    number: int,
+    name: str,
+) -> RegistryEntry:
+    """Assert that a sensor entity of a given type was registered properly."""
+    registry = get_entity_registry(hass)
+    unique_id = f"{serial_number}-{sensor_type}-{number}"
+
+    entity_id = registry.async_get_entity_id("sensor", DOMAIN, unique_id)
+    assert entity_id is not None
+
+    sensor = registry.async_get(entity_id)
+    assert sensor
+    assert sensor.unique_id == unique_id
+    assert sensor.original_name == name
+
+    return sensor
+
+
+@pytest.fixture
+def monitors() -> AsyncMock:
+    """Provide a mock greeneye.Monitors object that has listeners and can add new monitors."""
+    with patch("greeneye.Monitors", new=AsyncMock) as mock_monitors:
+        add_listeners(mock_monitors)
+        mock_monitors.monitors = {}
+
+        def add_monitor(monitor: MagicMock) -> None:
+            """Add the given mock monitor as a monitor with the given serial number, notifying any listeners on the Monitors object."""
+            serial_number = monitor.serial_number
+            mock_monitors.monitors[serial_number] = monitor
+            mock_monitors.notify_all_listeners(monitor)
+
+        mock_monitors.add_monitor = add_monitor
+        yield mock_monitors
diff --git a/tests/components/greeneye_monitor/test_init.py b/tests/components/greeneye_monitor/test_init.py
new file mode 100644
index 0000000000000000000000000000000000000000..143fb14f28ce67e12b1e6be191924989f804bf73
--- /dev/null
+++ b/tests/components/greeneye_monitor/test_init.py
@@ -0,0 +1,199 @@
+"""Tests for greeneye_monitor component initialization."""
+
+from __future__ import annotations
+
+from unittest.mock import AsyncMock
+
+import pytest
+
+from homeassistant.components.greeneye_monitor import (
+    CONF_MONITORS,
+    CONF_NUMBER,
+    CONF_SERIAL_NUMBER,
+    CONF_TEMPERATURE_SENSORS,
+    DOMAIN,
+)
+from homeassistant.const import (
+    CONF_NAME,
+    CONF_PORT,
+    CONF_SENSORS,
+    CONF_TEMPERATURE_UNIT,
+)
+from homeassistant.core import HomeAssistant
+from homeassistant.setup import async_setup_component
+
+from .common import (
+    SINGLE_MONITOR_CONFIG_NO_SENSORS,
+    SINGLE_MONITOR_CONFIG_POWER_SENSORS,
+    SINGLE_MONITOR_CONFIG_PULSE_COUNTERS,
+    SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS,
+    SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS,
+    SINGLE_MONITOR_SERIAL_NUMBER,
+    setup_greeneye_monitor_component_with_config,
+)
+from .conftest import (
+    assert_power_sensor_registered,
+    assert_pulse_counter_registered,
+    assert_temperature_sensor_registered,
+    assert_voltage_sensor_registered,
+)
+
+
+async def test_setup_fails_if_no_sensors_defined(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that component setup fails if there are no sensors defined in the YAML."""
+    success = await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_NO_SENSORS
+    )
+    assert not success
+
+
+@pytest.mark.xfail(reason="Currently failing. Will fix in subsequent PR.")
+async def test_setup_succeeds_no_config(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that component setup succeeds if there is no config present in the YAML."""
+    assert await async_setup_component(hass, DOMAIN, {})
+
+
+async def test_setup_creates_temperature_entities(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that component setup registers temperature sensors properly."""
+    assert await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS
+    )
+
+    assert_temperature_sensor_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "temp_a"
+    )
+    assert_temperature_sensor_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 2, "temp_2"
+    )
+    assert_temperature_sensor_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 3, "temp_c"
+    )
+    assert_temperature_sensor_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 4, "temp_d"
+    )
+    assert_temperature_sensor_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 5, "temp_5"
+    )
+    assert_temperature_sensor_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 6, "temp_f"
+    )
+    assert_temperature_sensor_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 7, "temp_g"
+    )
+    assert_temperature_sensor_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 8, "temp_h"
+    )
+
+
+async def test_setup_creates_pulse_counter_entities(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that component setup registers pulse counters properly."""
+    assert await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS
+    )
+
+    assert_pulse_counter_registered(
+        hass,
+        SINGLE_MONITOR_SERIAL_NUMBER,
+        1,
+        "pulse_a",
+        "pulses",
+        "s",
+    )
+    assert_pulse_counter_registered(
+        hass, SINGLE_MONITOR_SERIAL_NUMBER, 2, "pulse_2", "gal", "min"
+    )
+    assert_pulse_counter_registered(
+        hass,
+        SINGLE_MONITOR_SERIAL_NUMBER,
+        3,
+        "pulse_3",
+        "gal",
+        "h",
+    )
+    assert_pulse_counter_registered(
+        hass,
+        SINGLE_MONITOR_SERIAL_NUMBER,
+        4,
+        "pulse_d",
+        "pulses",
+        "s",
+    )
+
+
+async def test_setup_creates_power_sensor_entities(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that component setup registers power sensors correctly."""
+    assert await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS
+    )
+
+    assert_power_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "channel 1")
+    assert_power_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 2, "channel two")
+
+
+async def test_setup_creates_voltage_sensor_entities(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that component setup registers voltage sensors properly."""
+    assert await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
+    )
+
+    assert_voltage_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "voltage 1")
+
+
+async def test_multi_monitor_config(hass: HomeAssistant, monitors: AsyncMock) -> None:
+    """Test that component setup registers entities from multiple monitors correctly."""
+    assert await setup_greeneye_monitor_component_with_config(
+        hass,
+        {
+            DOMAIN: {
+                CONF_PORT: 7513,
+                CONF_MONITORS: [
+                    {
+                        CONF_SERIAL_NUMBER: "00000001",
+                        CONF_TEMPERATURE_SENSORS: {
+                            CONF_TEMPERATURE_UNIT: "C",
+                            CONF_SENSORS: [
+                                {CONF_NUMBER: 1, CONF_NAME: "unit_1_temp_1"}
+                            ],
+                        },
+                    },
+                    {
+                        CONF_SERIAL_NUMBER: "00000002",
+                        CONF_TEMPERATURE_SENSORS: {
+                            CONF_TEMPERATURE_UNIT: "F",
+                            CONF_SENSORS: [
+                                {CONF_NUMBER: 1, CONF_NAME: "unit_2_temp_1"}
+                            ],
+                        },
+                    },
+                ],
+            }
+        },
+    )
+
+    assert_temperature_sensor_registered(hass, 1, 1, "unit_1_temp_1")
+    assert_temperature_sensor_registered(hass, 2, 1, "unit_2_temp_1")
+
+
+async def test_setup_and_shutdown(hass: HomeAssistant, monitors: AsyncMock) -> None:
+    """Test that the component can set up and shut down cleanly, closing the underlying server on shutdown."""
+    server = AsyncMock()
+    monitors.start_server = AsyncMock(return_value=server)
+    assert await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS
+    )
+
+    await hass.async_stop()
+
+    assert server.close.called
diff --git a/tests/components/greeneye_monitor/test_sensor.py b/tests/components/greeneye_monitor/test_sensor.py
new file mode 100644
index 0000000000000000000000000000000000000000..63ab8b64423b77b71e8e1bf580272f3bd91c454d
--- /dev/null
+++ b/tests/components/greeneye_monitor/test_sensor.py
@@ -0,0 +1,165 @@
+"""Tests for greeneye_monitor sensors."""
+from unittest.mock import AsyncMock, MagicMock
+
+from homeassistant.components.greeneye_monitor.sensor import (
+    DATA_PULSES,
+    DATA_WATT_SECONDS,
+)
+from homeassistant.const import STATE_UNKNOWN
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity_registry import async_get as get_entity_registry
+
+from .common import (
+    SINGLE_MONITOR_CONFIG_POWER_SENSORS,
+    SINGLE_MONITOR_CONFIG_PULSE_COUNTERS,
+    SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS,
+    SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS,
+    SINGLE_MONITOR_SERIAL_NUMBER,
+    mock_monitor,
+    setup_greeneye_monitor_component_with_config,
+)
+from .conftest import assert_sensor_state
+
+
+async def test_disable_sensor_before_monitor_connected(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that a sensor disabled before its monitor connected stops listening for new monitors."""
+    # The sensor base class handles connecting the monitor, so we test this with a single voltage sensor for ease
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
+    )
+
+    assert len(monitors.listeners) == 1
+    await disable_entity(hass, "sensor.voltage_1")
+    assert len(monitors.listeners) == 0  # Make sure we cleaned up the listener
+
+
+async def test_updates_state_when_monitor_connected(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that a sensor updates its state when its monitor first connects."""
+    # The sensor base class handles updating the state on connection, so we test this with a single voltage sensor for ease
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
+    )
+
+    assert_sensor_state(hass, "sensor.voltage_1", STATE_UNKNOWN)
+    assert len(monitors.listeners) == 1
+    connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER)
+    assert len(monitors.listeners) == 0  # Make sure we cleaned up the listener
+    assert_sensor_state(hass, "sensor.voltage_1", "120.0")
+
+
+async def test_disable_sensor_after_monitor_connected(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that a sensor disabled after its monitor connected stops listening for sensor changes."""
+    # The sensor base class handles connecting the monitor, so we test this with a single voltage sensor for ease
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
+    )
+    monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER)
+
+    assert len(monitor.listeners) == 1
+    await disable_entity(hass, "sensor.voltage_1")
+    assert len(monitor.listeners) == 0
+
+
+async def test_updates_state_when_sensor_pushes(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that a sensor entity updates its state when the underlying sensor pushes an update."""
+    # The sensor base class handles triggering state updates, so we test this with a single voltage sensor for ease
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
+    )
+    monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER)
+    assert_sensor_state(hass, "sensor.voltage_1", "120.0")
+
+    monitor.voltage = 119.8
+    monitor.notify_all_listeners()
+    assert_sensor_state(hass, "sensor.voltage_1", "119.8")
+
+
+async def test_power_sensor_initially_unknown(
+    hass: HomeAssistant, monitors: AsyncMock
+) -> None:
+    """Test that the power sensor can handle its initial state being unknown (since the GEM API needs at least two packets to arrive before it can compute watts)."""
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS
+    )
+    connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER)
+    assert_sensor_state(
+        hass, "sensor.channel_1", STATE_UNKNOWN, {DATA_WATT_SECONDS: 1000}
+    )
+    # This sensor was configured with net metering on, so we should be taking the
+    # polarized value
+    assert_sensor_state(
+        hass, "sensor.channel_two", STATE_UNKNOWN, {DATA_WATT_SECONDS: -400}
+    )
+
+
+async def test_power_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None:
+    """Test that a power sensor reports its values correctly, including handling net metering."""
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS
+    )
+    monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER)
+    monitor.channels[0].watts = 120.0
+    monitor.channels[1].watts = 120.0
+    monitor.channels[0].notify_all_listeners()
+    monitor.channels[1].notify_all_listeners()
+    assert_sensor_state(hass, "sensor.channel_1", "120.0", {DATA_WATT_SECONDS: 1000})
+    # This sensor was configured with net metering on, so we should be taking the
+    # polarized value
+    assert_sensor_state(hass, "sensor.channel_two", "120.0", {DATA_WATT_SECONDS: -400})
+
+
+async def test_pulse_counter(hass: HomeAssistant, monitors: AsyncMock) -> None:
+    """Test that a pulse counter sensor reports its values properly, including calculating different units."""
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS
+    )
+    connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER)
+    assert_sensor_state(hass, "sensor.pulse_a", "10.0", {DATA_PULSES: 1000})
+    # This counter was configured with each pulse meaning 0.5 gallons and
+    # wanting to show gallons per minute, so 10 pulses per second -> 300 gal/min
+    assert_sensor_state(hass, "sensor.pulse_2", "300.0", {DATA_PULSES: 1000})
+    # This counter was configured with each pulse meaning 0.5 gallons and
+    # wanting to show gallons per hour, so 10 pulses per second -> 18000 gal/hr
+    assert_sensor_state(hass, "sensor.pulse_3", "18000.0", {DATA_PULSES: 1000})
+
+
+async def test_temperature_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None:
+    """Test that a temperature sensor reports its values properly, including proper handling of when its native unit is different from that configured in hass."""
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS
+    )
+    connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER)
+    # The config says that the sensor is reporting in Fahrenheit; if we set that up
+    # properly, HA will have converted that to Celsius by default.
+    assert_sensor_state(hass, "sensor.temp_a", "0.0")
+
+
+async def test_voltage_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None:
+    """Test that a voltage sensor reports its values properly."""
+    await setup_greeneye_monitor_component_with_config(
+        hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
+    )
+    connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER)
+    assert_sensor_state(hass, "sensor.voltage_1", "120.0")
+
+
+def connect_monitor(monitors: AsyncMock, serial_number: int) -> MagicMock:
+    """Simulate a monitor connecting to Home Assistant. Returns the mock monitor API object."""
+    monitor = mock_monitor(serial_number)
+    monitors.add_monitor(monitor)
+    return monitor
+
+
+async def disable_entity(hass: HomeAssistant, entity_id: str) -> None:
+    """Disable the given entity."""
+    entity_registry = get_entity_registry(hass)
+    entity_registry.async_update_entity(entity_id, disabled_by="user")
+    await hass.async_block_till_done()