Skip to main content

Full test coverage for the config flow

Reasoning​

The config flow is the first interaction a user has with your integration. It is important to ensure that the config flow is working as expected and that the user can set up the integration without any issues or (config flow related) errors.

This means that we want to have 100% test coverage for the config flow. In those tests, we require verification that the flow is able to recover from an error to confirm that the user is able to finish the flow even if something goes wrong. The happy flow tests should also assert the unique ID of the created config entry, so a change in how the unique ID is derived doesn't go unnoticed.

Since we want the user to have a smooth experience using other integration flows, this rule also applies to the reconfigure, reauthentication, and options flows.

The extra added benefit of having tests for an integration is that it introduces the developer to testing, making it easier to write tests for other parts of the integration.

warning

Even though the code used to check the uniqueness of a config entry is most likely touched by the happy flow tests, make sure to also test that the flow doesn't allow adding more than one unique configuration entry to reach complete coverage.

Example implementation​

We need to test the following scenarios for each way the config flow can be initiated, either by the user, by discovery, or by an import flow.

The example below shows a basic happy flow initiated by the user.

test_config_flow.py:

async def test_full_flow(
hass: HomeAssistant,
mock_my_client: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test full flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "10.0.0.131"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "My integration"
assert result["data"] == {
CONF_HOST: "10.0.0.131",
}
assert result["result"].unique_id == "ABC123"

Every error the flow can show needs a test, for example a connection error, invalid credentials, an invalid field value or an unexpected exception. After each error, the test fixes the cause and finishes the flow, to show the user can recover from the error. The flow is finished when it creates the entry, or for the reauthentication and reconfigure flows, when it aborts with reauth_successful or reconfigure_successful. The example below shows this for the user step.

test_config_flow.py:

@pytest.mark.parametrize(
("exception", "error"),
[
(MyConnectionError, "cannot_connect"),
(MyAuthenticationError, "invalid_auth"),
(Exception, "unknown"),
],
)
async def test_flow_errors(
hass: HomeAssistant,
mock_my_client: AsyncMock,
mock_setup_entry: AsyncMock,
exception: Exception,
error: str,
) -> None:
"""Test we handle errors and recover from them."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)

mock_my_client.get_data.side_effect = exception

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "10.0.0.131"},
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": error}

mock_my_client.get_data.side_effect = None

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "10.0.0.131"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY

Additional resources​

More information about config flows can be found in the config flow documentation. More information about testing integrations can be found in the testing documentation.

Exceptions​

There are no exceptions to this rule.