diff --git a/.coveragerc b/.coveragerc
index d91825943561b19656ff67f6c11dd71b9fc324af..12095eef2472145bf1868a57dae9903008fcdf2b 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -389,7 +389,6 @@ omit =
     homeassistant/components/firmata/pin.py
     homeassistant/components/firmata/sensor.py
     homeassistant/components/firmata/switch.py
-    homeassistant/components/fitbit/*
     homeassistant/components/fivem/__init__.py
     homeassistant/components/fivem/binary_sensor.py
     homeassistant/components/fivem/coordinator.py
diff --git a/homeassistant/components/fitbit/const.py b/homeassistant/components/fitbit/const.py
index 1578359356d2056da7dfd06d003c038400ae0f20..045b58cfc5e3a0800312b2525b71c69e187a6a5b 100644
--- a/homeassistant/components/fitbit/const.py
+++ b/homeassistant/components/fitbit/const.py
@@ -12,6 +12,8 @@ from homeassistant.const import (
     UnitOfVolume,
 )
 
+DOMAIN: Final = "fitbit"
+
 ATTR_ACCESS_TOKEN: Final = "access_token"
 ATTR_REFRESH_TOKEN: Final = "refresh_token"
 ATTR_LAST_SAVED_AT: Final = "last_saved_at"
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index 3c469e25e54b10b450651c4e99a3165e8c13b671..ce69841233bc14cd10bdef4691da9524ef4e5c54 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -629,6 +629,9 @@ feedparser==6.0.10
 # homeassistant.components.file
 file-read-backwards==2.0.0
 
+# homeassistant.components.fitbit
+fitbit==0.3.1
+
 # homeassistant.components.fivem
 fivem-api==0.1.2
 
diff --git a/tests/components/fitbit/__init__.py b/tests/components/fitbit/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b639a3faa86a235ce53825b67529da89e18d7db
--- /dev/null
+++ b/tests/components/fitbit/__init__.py
@@ -0,0 +1 @@
+"""Tests for fitbit component."""
diff --git a/tests/components/fitbit/conftest.py b/tests/components/fitbit/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3e5bbd1d1862507a7a5ba04a6549d6f881fcfd4
--- /dev/null
+++ b/tests/components/fitbit/conftest.py
@@ -0,0 +1,167 @@
+"""Test fixtures for fitbit."""
+
+from collections.abc import Awaitable, Callable, Generator
+import datetime
+from http import HTTPStatus
+import time
+from typing import Any
+from unittest.mock import patch
+
+import pytest
+from requests_mock.mocker import Mocker
+
+from homeassistant.components.fitbit.const import DOMAIN
+from homeassistant.core import HomeAssistant
+from homeassistant.setup import async_setup_component
+
+CLIENT_ID = "1234"
+CLIENT_SECRET = "5678"
+PROFILE_USER_ID = "fitbit-api-user-id-1"
+FAKE_TOKEN = "some-token"
+FAKE_REFRESH_TOKEN = "some-refresh-token"
+
+PROFILE_API_URL = "https://api.fitbit.com/1/user/-/profile.json"
+DEVICES_API_URL = "https://api.fitbit.com/1/user/-/devices.json"
+TIMESERIES_API_URL_FORMAT = (
+    "https://api.fitbit.com/1/user/-/{resource}/date/today/7d.json"
+)
+
+
+@pytest.fixture(name="token_expiration_time")
+def mcok_token_expiration_time() -> float:
+    """Fixture for expiration time of the config entry auth token."""
+    return time.time() + 86400
+
+
+@pytest.fixture(name="fitbit_config_yaml")
+def mock_fitbit_config_yaml(token_expiration_time: float) -> dict[str, Any]:
+    """Fixture for the yaml fitbit.conf file contents."""
+    return {
+        "access_token": FAKE_TOKEN,
+        "refresh_token": FAKE_REFRESH_TOKEN,
+        "last_saved_at": token_expiration_time,
+    }
+
+
+@pytest.fixture(name="fitbit_config_setup", autouse=True)
+def mock_fitbit_config_setup(
+    fitbit_config_yaml: dict[str, Any],
+) -> Generator[None, None, None]:
+    """Fixture to mock out fitbit.conf file data loading and persistence."""
+
+    with patch(
+        "homeassistant.components.fitbit.sensor.os.path.isfile", return_value=True
+    ), patch(
+        "homeassistant.components.fitbit.sensor.load_json_object",
+        return_value=fitbit_config_yaml,
+    ), patch(
+        "homeassistant.components.fitbit.sensor.save_json",
+    ):
+        yield
+
+
+@pytest.fixture(name="monitored_resources")
+def mock_monitored_resources() -> list[str] | None:
+    """Fixture for the fitbit yaml config monitored_resources field."""
+    return None
+
+
+@pytest.fixture(name="sensor_platform_config")
+def mock_sensor_platform_config(
+    monitored_resources: list[str] | None,
+) -> dict[str, Any]:
+    """Fixture for the fitbit sensor platform configuration data in configuration.yaml."""
+    config = {}
+    if monitored_resources is not None:
+        config["monitored_resources"] = monitored_resources
+    return config
+
+
+@pytest.fixture(name="sensor_platform_setup")
+async def mock_sensor_platform_setup(
+    hass: HomeAssistant,
+    sensor_platform_config: dict[str, Any],
+) -> Callable[[], Awaitable[bool]]:
+    """Fixture to set up the integration."""
+
+    async def run() -> bool:
+        result = await async_setup_component(
+            hass,
+            "sensor",
+            {
+                "sensor": [
+                    {
+                        "platform": DOMAIN,
+                        **sensor_platform_config,
+                    }
+                ]
+            },
+        )
+        await hass.async_block_till_done()
+        return result
+
+    return run
+
+
+@pytest.fixture(name="profile_id")
+async def mock_profile_id() -> str:
+    """Fixture for the profile id returned from the API response."""
+    return PROFILE_USER_ID
+
+
+@pytest.fixture(name="profile", autouse=True)
+async def mock_profile(requests_mock: Mocker, profile_id: str) -> None:
+    """Fixture to setup fake requests made to Fitbit API during config flow."""
+    requests_mock.register_uri(
+        "GET",
+        PROFILE_API_URL,
+        status_code=HTTPStatus.OK,
+        json={
+            "user": {
+                "encodedId": profile_id,
+                "fullName": "My name",
+                "locale": "en_US",
+            },
+        },
+    )
+
+
+@pytest.fixture(name="devices_response")
+async def mock_device_response() -> list[dict[str, Any]]:
+    """Return the list of devices."""
+    return []
+
+
+@pytest.fixture(autouse=True)
+async def mock_devices(requests_mock: Mocker, devices_response: dict[str, Any]) -> None:
+    """Fixture to setup fake device responses."""
+    requests_mock.register_uri(
+        "GET",
+        DEVICES_API_URL,
+        status_code=HTTPStatus.OK,
+        json=devices_response,
+    )
+
+
+def timeseries_response(resource: str, value: str) -> dict[str, Any]:
+    """Create a timeseries response value."""
+    return {
+        resource: [{"dateTime": datetime.datetime.today().isoformat(), "value": value}]
+    }
+
+
+@pytest.fixture(name="register_timeseries")
+async def mock_register_timeseries(
+    requests_mock: Mocker,
+) -> Callable[[str, dict[str, Any]], None]:
+    """Fixture to setup fake timeseries API responses."""
+
+    def register(resource: str, response: dict[str, Any]) -> None:
+        requests_mock.register_uri(
+            "GET",
+            TIMESERIES_API_URL_FORMAT.format(resource=resource),
+            status_code=HTTPStatus.OK,
+            json=response,
+        )
+
+    return register
diff --git a/tests/components/fitbit/test_sensor.py b/tests/components/fitbit/test_sensor.py
new file mode 100644
index 0000000000000000000000000000000000000000..6918a712f72cb8c0f0085aa2f555078a0e065702
--- /dev/null
+++ b/tests/components/fitbit/test_sensor.py
@@ -0,0 +1,92 @@
+"""Tests for the fitbit sensor platform."""
+
+
+from collections.abc import Awaitable, Callable
+from typing import Any
+
+import pytest
+
+from homeassistant.core import HomeAssistant
+
+from .conftest import timeseries_response
+
+DEVICE_RESPONSE_CHARGE_2 = {
+    "battery": "Medium",
+    "batteryLevel": 60,
+    "deviceVersion": "Charge 2",
+    "id": "816713257",
+    "lastSyncTime": "2019-11-07T12:00:58.000",
+    "mac": "16ADD56D54GD",
+    "type": "TRACKER",
+}
+DEVICE_RESPONSE_ARIA_AIR = {
+    "battery": "High",
+    "batteryLevel": 95,
+    "deviceVersion": "Aria Air",
+    "id": "016713257",
+    "lastSyncTime": "2019-11-07T12:00:58.000",
+    "mac": "06ADD56D54GD",
+    "type": "SCALE",
+}
+
+
+@pytest.mark.parametrize(
+    "monitored_resources",
+    [["activities/steps"]],
+)
+async def test_step_sensor(
+    hass: HomeAssistant,
+    sensor_platform_setup: Callable[[], Awaitable[bool]],
+    register_timeseries: Callable[[str, dict[str, Any]], None],
+) -> None:
+    """Test battery level sensor."""
+
+    register_timeseries(
+        "activities/steps", timeseries_response("activities-steps", "5600")
+    )
+    await sensor_platform_setup()
+
+    state = hass.states.get("sensor.steps")
+    assert state
+    assert state.state == "5600"
+    assert state.attributes == {
+        "attribution": "Data provided by Fitbit.com",
+        "friendly_name": "Steps",
+        "icon": "mdi:walk",
+        "unit_of_measurement": "steps",
+    }
+
+
+@pytest.mark.parametrize(
+    ("devices_response", "monitored_resources"),
+    [([DEVICE_RESPONSE_CHARGE_2, DEVICE_RESPONSE_ARIA_AIR], ["devices/battery"])],
+)
+async def test_device_battery_level(
+    hass: HomeAssistant,
+    sensor_platform_setup: Callable[[], Awaitable[bool]],
+) -> None:
+    """Test battery level sensor for devices."""
+
+    await sensor_platform_setup()
+
+    state = hass.states.get("sensor.charge_2_battery")
+    assert state
+    assert state.state == "Medium"
+    assert state.attributes == {
+        "attribution": "Data provided by Fitbit.com",
+        "friendly_name": "Charge 2 Battery",
+        "icon": "mdi:battery-50",
+        "model": "Charge 2",
+        "type": "tracker",
+    }
+
+    state = hass.states.get("sensor.aria_air_battery")
+    assert state
+    assert state.state == "High"
+    assert state.attributes == {
+        "attribution": "Data provided by Fitbit.com",
+        "friendly_name": "Aria Air Battery",
+        "icon": "mdi:battery",
+        "model": "Aria Air",
+        "type": "scale",
+    }