HelloThisIsFlo/Appdaemon-Test-Framework

Notify api cannot be used in tests

Closed this issue ยท 2 comments

I have a test that calls the notify api on appdaemon apps such as:

class ExampleApp(hass.Hass):
    def initialize(self):
        self.notify(message="test", name="html5")

This does not work in the tests because the AD attribute never get instantiated and raises an AttributeError here:

def hass_check(func):
    def func_wrapper(*args, **kwargs):
        self = args[0]
        if not self.AD.get_plugin(self._get_namespace(**kwargs)).reading_messages:
            self.AD.log("WARNING", "Attempt to call Home Assistant while disconnected: {}".format(func))
            return lambda *args: None
        else:
            return func(*args, **kwargs)

    return (func_wrapper)

I could certainly overwrite the notify method to call self.call_service directly, but it seems like it would be better to test against the current appdaemon API. I was able to monkey patch the AD attribute, but I then ran into another issue with the app's namespace never being set. I had to call app.set_namespace("default") to get past that, but it seems these should be handled in the test initialization.

Hi,

Thanks for reporting the issue ๐Ÿ™‚

The current design of assert_that does not allow to directly perform assertions on all the helper methods of the Hass class. In all my projects calling services and turning on/off entities represents 90% of the work being done so I didn't try to find a way to nicely integrate the other features of the Appdaemon framework.

That being said, there is a way to test for helper methods, they just need to first be patched. Then, all patched methods of the Hass object can be accessed via an injectable pytest fixture: hass_functions. It's briefly mentioned in the doc.

I just patched the notify function, so using hass_functions with notify will be working after you update the appdaemontestframework.

In practice, this is how it will look like:

class ExampleApp(hass.Hass):
    def initialize(self):
        pass

    def do_something_that_calls_notify(self):
        ...
        self.notify(message="test", name="html5")
        ...

@automation_fixture(ExampleApp)
def example_app():
    pass

def test_notify(given_that, example_app, hass_functions):
    example_app.do_something_that_calls_notify()
    hass_functions['notify'].assert_called_with(message="test", name="html5")

I also added a test to confirm the functionality is working: Test Extra Hass Functions

If you need to call it during initialize like in your example, no problem simply call initialize again in your test. The @mock_automation fixture is taking care of cleaning the mocks calls that might have been performed during the initialization phase so that you end up with a clean state at the beginning of the test. Again, in practice:

class ExampleApp(hass.Hass):
    def initialize(self):
        self.notify(message="during init", name="html5")


@automation_fixture(ExampleApp)
def example_app():
    pass

def test_notify_during_init(given_that, example_app, hass_functions):
    hass_functions['notify'].assert_not_called()
    example_app.initialize()
    hass_functions['notify'].assert_called_with(message="during init", name="html5")

Update to version 2.3.2 and let me know how it's working out for you ๐Ÿ˜‰

Thanks, that should work for what I need.