Pytest-homeassistant-custom-component

Out of frustration in figuring out how to write tests for custom components, I have created a, hopefully, easy to use library. It aims to enable all the fixtures and helpers that exist in homeassistant/core. It is semi-automatically updated from the latest dev branch in homeassistant/core, with the goal of being fully automatic in the future.

I hope others may find it useful, and that it leads to less frustrating dev experience for custom component developers.

For all those HACS components, you will need to add an __init__.py file in your custom_components folder.

2 Likes

Very nice, I had created something similar (https://github.com/boralyl/pytest-homeassistant) by pulling out fixtures, but it’s not automatically updated at all and has broken before w/ new releases. I’ll have to dig into how you went about this I’m not terribly happy with my solution.

Here’s a post I made about unit testing custom components: https://aarongodfrey.dev/home%20automation/building_a_home_assistant_custom_component_part_2/

1 Like

To be very clear, I did see your package, and considered contributing there, but I couldn’t tell how you were deciding to choose which pieces to incorporate. I’d be very willing to accept contributions if you see any improvements that could be made.

Everything ‘happens’ in the generate_package.py file and the GitHub actions.

I also added an acknowledgement to your package in the README.

I’d love to use this, but I’m not sure how to leverage it and would love your thoughts.

Trying to create tests for https://github.com/moralmunky/Home-Assistant-Mail-And-Packages

fahr’s blog post gives a decent overview. You pip install pytest-homeassistant-custom-component or pip install pytest-homeassistant, if you want to use fahr’s version. I would suggest doing this in a virtual environment.

Then you write your tests just like in the home-assistant/core repository in a tests. Then run the tests with pytest. There is an example custom component and tests in my repository as well.

1 Like

I took a quick look through the repository you linked. I would probably start by mocking the update_method of the EmailData class and using MockConfigEntry to set up your integration to bypass the config flow logic. Then you would have a test for only the workings of the sensor entity. Then you could mock out the actual IO and test the logic of the EmailData class only.

You could also start from the other end by mocking out any IO in the config flow and test that your config flow works the way it should form by form.

2 Likes

Honestly, I just pulled out the fixtures that I ended up using in my custom components, and ignored the rest. I really like your implementation since it’s automated and probably less likely to run into issues with upgrades.

Ya not sure how to actually do that :frowning:

This I have done.

and lost again :frowning:

https://docs.python.org/3/library/unittest.mock.html

I suggest using the patch function in your test. You’ll need to put an __init__.py in the custom_components folder too. This a rabbit hole to fall in, but you can then make changes to the custom component more confidently.

Something like this:

async def test_some_stuff(hass):

with patch("custom_components.myintegration.sensor.EmailData.update") as mock_update:
    # do some stuff with a mocked method

1 Like

ah ok that makes sense, I’m actually migrating all the functions and such out of the sensor.py into __init__.py and keeping only the sensor bits in there.

Thanks for the pointer!

Sorry to be a bother, I have this most of the way done, but I’m getting stuck and would like to pick your brain if you’re game.

I am getting the following error (during pytest):

Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.7.9/x64/lib/python3.7/site-packages/homeassistant/helpers/entity_platform.py", line 193, in _async_setup_platform
    await asyncio.shield(task)
  File "/home/runner/work/Home-Assistant-Mail-And-Packages/Home-Assistant-Mail-And-Packages/custom_components/mail_and_packages/sensor.py", line 70, in async_setup_entry
    data = EmailData(hass, config)
  File "/home/runner/work/Home-Assistant-Mail-And-Packages/Home-Assistant-Mail-And-Packages/custom_components/mail_and_packages/sensor.py", line 101, in __init__
    self.update = Throttle(self._scan_interval)(self.update)
  File "/opt/hostedtoolcache/Python/3.7.9/x64/lib/python3.7/site-packages/homeassistant/util/__init__.py", line 206, in __call__
    and "." not in method.__qualname__.split(".<locals>.")[-1]
  File "/opt/hostedtoolcache/Python/3.7.9/x64/lib/python3.7/unittest/mock.py", line 601, in __getattr__
    raise AttributeError(name)
AttributeError: __qualname__

Seems it doesn’t like the self.update's Throttle?
(I am switching this out to use the update coordinator in a yet to be released version.)

It looks like you are trying to dynamically update the fetch interval?

If you are using DataUpdateCoordinator, there is no need to use Throttle explicitly. The coordinator class has a built in debouncer. If the default debouncer time doesn’t work for you, you can adjust it. The coordinator also makes it easy to dynamically update the fetch interval.

I’m not sure to your actual question. Your method doesn’t have __qualname__ for sure, but I’m not sure what this means.

Ya I think I’ll give it a try on my updated version that’s using the coordinator see if that pans out.

Thanks again!