-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add tests using ludeeus/integration_blueprint#50 as base
- Loading branch information
Showing
10 changed files
with
283 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
homeassistant |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
-r requirements_dev.txt | ||
pytest | ||
pytest-homeassistant-custom-component |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Tests for {{cookiecutter.project_name}} integration.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
"""Global fixtures for {{cookiecutter.project_name}} integration.""" | ||
from unittest import mock | ||
|
||
import pytest | ||
from pytest_homeassistant_custom_component.async_mock import patch | ||
|
||
pytest_plugins = "pytest_homeassistant_custom_component" | ||
|
||
|
||
@pytest.fixture(name="skip_notifications", autouse=True) | ||
def skip_notifications_fixture(): | ||
"""Skip notification calls.""" | ||
with patch("homeassistant.components.persistent_notification.async_create"), patch( | ||
"homeassistant.components.persistent_notification.async_dismiss" | ||
): | ||
yield | ||
|
||
|
||
@pytest.fixture(name="bypass_get_data") | ||
def bypass_get_data_fixture(): | ||
"""Skip calls to get data from API.""" | ||
with patch( | ||
"custom_components.{{cookiecutter.project_name}}.{{cookiecutter.class_name_prefix}}ApiClient.async_get_data" | ||
): | ||
yield | ||
|
||
|
||
@pytest.fixture(name="error_on_get_data") | ||
def error_get_data_fixture(): | ||
"""Simulate error when retrieving data from API.""" | ||
with patch( | ||
"custom_components.{{cookiecutter.project_name}}.{{cookiecutter.class_name_prefix}}ApiClient.async_get_data", | ||
side_effect=Exception, | ||
): | ||
yield |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"""Constants for {{cookiecutter.project_name}} tests.""" | ||
from custom_components.{{cookiecutter.project_name}}.const import CONF_PASSWORD, CONF_USERNAME | ||
|
||
MOCK_CONFIG = {CONF_USERNAME: "test_username", CONF_PASSWORD: "test_password"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
"""Tests for {{cookiecutter.project_name}} api.""" | ||
import asyncio | ||
|
||
import aiohttp | ||
from homeassistant.helpers.aiohttp_client import async_get_clientsession | ||
|
||
from custom_components.{{cookiecutter.project_name}}.api import {{cookiecutter.class_name_prefix}}ApiClient | ||
|
||
|
||
async def test_ap(hass, aioclient_mock, caplog): | ||
"""Test API calls.""" | ||
|
||
api = {{cookiecutter.class_name_prefix}}ApiClient("test", "test", async_get_clientsession(hass)) | ||
|
||
aioclient_mock.get( | ||
"https://jsonplaceholder.typicode.com/posts/1", json={"test": "test"} | ||
) | ||
assert await api.async_get_data() == {"test": "test"} | ||
|
||
aioclient_mock.patch("https://jsonplaceholder.typicode.com/posts/1") | ||
assert await api.async_set_title("test") is None | ||
|
||
caplog.clear() | ||
aioclient_mock.put( | ||
"https://jsonplaceholder.typicode.com/posts/1", exc=asyncio.TimeoutError | ||
) | ||
assert ( | ||
await api.api_wrapper("put", "https://jsonplaceholder.typicode.com/posts/1") | ||
is None | ||
) | ||
assert ( | ||
len(caplog.record_tuples) == 1 | ||
and "Timeout error fetching information from" in caplog.record_tuples[0][2] | ||
) | ||
|
||
caplog.clear() | ||
aioclient_mock.post( | ||
"https://jsonplaceholder.typicode.com/posts/1", exc=aiohttp.ClientError | ||
) | ||
assert ( | ||
await api.api_wrapper("post", "https://jsonplaceholder.typicode.com/posts/1") | ||
is None | ||
) | ||
assert ( | ||
len(caplog.record_tuples) == 1 | ||
and "Error fetching information from" in caplog.record_tuples[0][2] | ||
) | ||
|
||
caplog.clear() | ||
aioclient_mock.post("https://jsonplaceholder.typicode.com/posts/2", exc=Exception) | ||
assert ( | ||
await api.api_wrapper("post", "https://jsonplaceholder.typicode.com/posts/2") | ||
is None | ||
) | ||
assert ( | ||
len(caplog.record_tuples) == 1 | ||
and "Something really wrong happened!" in caplog.record_tuples[0][2] | ||
) | ||
|
||
caplog.clear() | ||
aioclient_mock.post("https://jsonplaceholder.typicode.com/posts/3", exc=TypeError) | ||
assert ( | ||
await api.api_wrapper("post", "https://jsonplaceholder.typicode.com/posts/3") | ||
is None | ||
) | ||
assert ( | ||
len(caplog.record_tuples) == 1 | ||
and "Error parsing information from" in caplog.record_tuples[0][2] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
"""Test {{cookiecutter.project_name}} config flow.""" | ||
from homeassistant import config_entries, data_entry_flow | ||
from pytest_homeassistant_custom_component.common import MockConfigEntry | ||
|
||
from custom_components.{{cookiecutter.project_name}}.const import ( | ||
BINARY_SENSOR, | ||
DOMAIN, | ||
PLATFORMS, | ||
SENSOR, | ||
SWITCH, | ||
) | ||
|
||
from .const import MOCK_CONFIG | ||
|
||
|
||
async def test_successful_config_flow(hass, bypass_get_data): | ||
"""Test a successful config flow.""" | ||
result = await hass.config_entries.flow.async_init( | ||
DOMAIN, context={"source": config_entries.SOURCE_USER} | ||
) | ||
|
||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM | ||
assert result["step_id"] == "user" | ||
|
||
result = await hass.config_entries.flow.async_configure( | ||
result["flow_id"], user_input=MOCK_CONFIG | ||
) | ||
|
||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY | ||
assert result["title"] == "test_username" | ||
assert result["data"] == MOCK_CONFIG | ||
assert result["result"] | ||
|
||
|
||
async def test_failed_config_flow(hass, error_on_get_data): | ||
"""Test a failed config flow due to credential validation failure.""" | ||
|
||
result = await hass.config_entries.flow.async_init( | ||
DOMAIN, context={"source": config_entries.SOURCE_USER} | ||
) | ||
|
||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM | ||
assert result["step_id"] == "user" | ||
|
||
result = await hass.config_entries.flow.async_configure( | ||
result["flow_id"], user_input=MOCK_CONFIG | ||
) | ||
|
||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM | ||
assert result["errors"] == {"base": "auth"} | ||
|
||
|
||
async def test_options_flow(hass): | ||
"""Test an options flow.""" | ||
entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") | ||
entry.add_to_hass(hass) | ||
result = await hass.config_entries.options.async_init(entry.entry_id) | ||
|
||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM | ||
assert result["step_id"] == "user" | ||
|
||
result = await hass.config_entries.options.async_configure( | ||
result["flow_id"], | ||
user_input={platform: platform != SENSOR for platform in PLATFORMS}, | ||
) | ||
|
||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY | ||
assert result["title"] == "test_username" | ||
|
||
assert entry.options == {BINARY_SENSOR: True, SENSOR: False, SWITCH: True} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
"""Test {{cookiecutter.project_name}} setup process.""" | ||
from homeassistant.exceptions import ConfigEntryNotReady | ||
import pytest | ||
from pytest_homeassistant_custom_component.common import MockConfigEntry | ||
|
||
from custom_components.{{cookiecutter.project_name}} import ( | ||
{{cookiecutter.class_name_prefix}}DataUpdateCoordinator, | ||
async_reload_entry, | ||
async_setup_entry, | ||
async_unload_entry, | ||
) | ||
from custom_components.{{cookiecutter.project_name}}.const import DOMAIN | ||
|
||
from .const import MOCK_CONFIG | ||
|
||
|
||
# We can pass fixtures as defined in conftest.py to tell pytest to use the fixture | ||
# for a given test. We can also leverage fixtures and mocks that are available in | ||
# Home Assistant using the pytest_homeassistant_custom_component plugin. | ||
# Assertions allow you to verify that the return value of whatever is on the left | ||
# side of the assertion matches with the right side. | ||
async def test_setup_unload_and_reload_entry(hass, bypass_get_data): | ||
"""Test entry setup and unload.""" | ||
# Create a mock entry so we don't have to go through config flow | ||
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") | ||
|
||
# Set up the entry and assert that the values set during setup are where we expect | ||
# them to be. Because we have patched the {{cookiecutter.class_name_prefix}}DataUpdateCoordinator.async_get_data | ||
# call, no code from custom_components/{{cookiecutter.project_name}}/api.py actually runs. | ||
assert await async_setup_entry(hass, config_entry) | ||
assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] | ||
assert ( | ||
type(hass.data[DOMAIN][config_entry.entry_id]) == {{cookiecutter.class_name_prefix}}DataUpdateCoordinator | ||
) | ||
|
||
# Reload the entry and assert that the data from above is still there | ||
assert await async_reload_entry(hass, config_entry) is None | ||
assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] | ||
assert ( | ||
type(hass.data[DOMAIN][config_entry.entry_id]) == {{cookiecutter.class_name_prefix}}DataUpdateCoordinator | ||
) | ||
|
||
# Unload the entry and verify that the data has been removed | ||
assert await async_unload_entry(hass, config_entry) | ||
assert config_entry.entry_id not in hass.data[DOMAIN] | ||
|
||
|
||
async def test_setup_entry_exception(hass, error_on_get_data): | ||
"""Test ConfigEntryNotReady when API raises an exception during entry setup.""" | ||
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") | ||
|
||
# In this case we are testing the condition where async_setup_entry raises | ||
# ConfigEntryNotReady using the `error_on_get_data` fixture which simulates | ||
# an error. | ||
with pytest.raises(ConfigEntryNotReady): | ||
assert await async_setup_entry(hass, config_entry) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
"""Test {{cookiecutter.project_name}} switch.""" | ||
from homeassistant.components.switch import SERVICE_TURN_OFF, SERVICE_TURN_ON | ||
from homeassistant.const import ATTR_ENTITY_ID | ||
from pytest_homeassistant_custom_component.async_mock import call, patch | ||
from pytest_homeassistant_custom_component.common import MockConfigEntry | ||
|
||
from custom_components.{{cookiecutter.project_name}} import async_setup_entry | ||
from custom_components.{{cookiecutter.project_name}}.const import DEFAULT_NAME, DOMAIN, SWITCH | ||
|
||
from .const import MOCK_CONFIG | ||
|
||
|
||
async def test_switch_services(hass): | ||
"""Test switch services.""" | ||
# Create a mock entry so we don't have to go through config flow | ||
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") | ||
assert await async_setup_entry(hass, config_entry) | ||
await hass.async_block_till_done() | ||
|
||
# Functions/objects can be patched directly in test code as well and can be used to test | ||
# additional things, like whether a function was called or what arguments it was called with | ||
with patch( | ||
"custom_components.{{cookiecutter.project_name}}.{{cookiecutter.class_name_prefix}}ApiClient.async_set_title" | ||
) as title_func: | ||
await hass.services.async_call( | ||
SWITCH, | ||
SERVICE_TURN_OFF, | ||
service_data={ATTR_ENTITY_ID: f"{SWITCH}.{DEFAULT_NAME}_{SWITCH}"}, | ||
blocking=True, | ||
) | ||
assert title_func.called | ||
assert title_func.call_args == call("foo") | ||
|
||
title_func.reset_mock() | ||
|
||
await hass.services.async_call( | ||
SWITCH, | ||
SERVICE_TURN_ON, | ||
service_data={ATTR_ENTITY_ID: f"{SWITCH}.{DEFAULT_NAME}_{SWITCH}"}, | ||
blocking=True, | ||
) | ||
assert title_func.called | ||
assert title_func.call_args == call("bar") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters