pythonmockingpytest

How to use `@pytest.mark.parametrize` and include an item for the default mock behavior?


I am creating a parameterized Mock PyTest to test API behaviors. I am trying to simplify the test code by testing the instance modified behavior, e.g. throw and exception, and the default behavior, i.e. load JSON from file vs. calling REST API.

I do not know how to add an array entry to represent the "default" mock behavior?

@pytest.mark.parametrize(
    ("get_nearby_sensors_mock", "get_nearby_sensors_errors"),
    [
        (AsyncMock(side_effect=Exception), {CONF_BASE: CONF_UNKNOWN}),
        (AsyncMock(side_effect=PurpleAirError), {CONF_BASE: CONF_UNKNOWN}),
        (AsyncMock(side_effect=InvalidApiKeyError), {CONF_BASE: CONF_INVALID_API_KEY}),
        (AsyncMock(return_value=[]), {CONF_BASE: CONF_NO_SENSORS_FOUND}),
        # What do I do here?
        # (AsyncMock(api.sensors, "async_get_nearby_sensors")) does not work as api is not in scope?
        # (AsyncMock(side_effect=None), {}) does not call the default fixture?
        (AsyncMock(), {}),
    ],
)
async def test_validate_coordinates(
    hass: HomeAssistant,
    mock_aiopurpleair,
    api,
    get_nearby_sensors_mock,
    get_nearby_sensors_errors,
) -> None:
    """Test validate_coordinates errors."""

    with (
        patch.object(api, "async_check_api_key"),
        patch.object(api.sensors, "async_get_nearby_sensors", get_nearby_sensors_mock),
    ):
        result: ConfigValidation = await ConfigValidation.async_validate_coordinates(
            hass, TEST_API_KEY, TEST_LATITUDE, TEST_LONGITUDE, TEST_RADIUS
        )
        assert result.errors == get_nearby_sensors_errors
        if result.errors == {}:
            assert result.data is not None
        else:
            assert result.data is None

How do I add a parameter for the "default behavior" of patch.object(api.sensors, "async_get_nearby_sensors") that will use the fixture to load data from canned JSON file?

Why mock; async_validate_coordinates() calls async_check_api_key() that needs to be mocked to pass, and async_get_nearby_sensors() that is mocked with a fixture to return data from a JSON file.

For ref this is the conftest.py file.


Solution

  • As a workaround, you could deal with any api-related case inside the function, where the api is known.
    For this single-use, I would use None to trigger the default case

    @pytest.mark.parametrize(
        ("get_nearby_sensors_mock", "get_nearby_sensors_errors"),
        [
            # [...]
            (None, {}),
        ],
    )
    async def test_validate_coordinates(
        hass: HomeAssistant,
        mock_aiopurpleair,
        api,
        get_nearby_sensors_mock,
        get_nearby_sensors_errors,
    ) -> None:
        """Test validate_coordinates errors."""
        if get_nearby_sensors_mock is None:
            get_nearby_sensors_mock = AsyncMock(api.sensors, "async_get_nearby_sensors")
    
        with (patch.object( # [...]