time_travel does not work (possibly incorrect use)
danobot opened this issue ยท 4 comments
Hi,
I'm a developer and am having some problems getting the time travel to work,
import pytest
from apps.demo_class import MotionLight
# Important:
# For this example to work, do not forget to copy the `conftest.py` file.
# See README.md for more info
TEST_LIGHT = 'light.test_light';
TEST_SENSOR = 'binary_sensor.test_sensor';
DELAY = 120;
@pytest.fixture
def motion_light(given_that):
motion_light = MotionLight(None, None, None, None, None, None, None, None)
given_that.time_is(0)
motion_light.initialize()
given_that.mock_functions_are_cleared()
return motion_light
def test_demo(given_that, motion_light, assert_that, time_travel):
given_that.state_of('light.test_light').is_set_to('on')
time_travel.assert_current_time(0).seconds()
motion_light.motion(DELAY)
time_travel.fast_forward(DELAY).seconds()
time_travel.assert_current_time(DELAY).seconds()
assert_that('light.test_light').was.turned_off()
Here is the demo class i am trying to test:
import appdaemon.plugins.hass.hassapi as hass
class MotionLight(hass.Hass):
def initialize(self):
self.timer = None
def motion(self, delay):
"""
Sensor callback: Called when the supplied sensor/s change state.
"""
self.start_timer(delay);
def start_timer(self, delay):
if self.timer:
self.cancel_timer(self.timer) # cancel previous timer
self.timer = self.run_in(self.light_off, delay)
def light_off(self):
self.log("fds");
self.turn_off('light.test_light')
For some reason, even though run_in time and time_travel time match, the turn off service call is not found.
=================================== FAILURES ===================================
__________________________________ test_demo ___________________________________
given_that = <appdaemontestframework.given_that.GivenThatWrapper object at 0x7fde397b00f0>
motion_light = <apps.demo_class.MotionLight object at 0x7fde39755a90>
assert_that = <appdaemontestframework.assert_that.AssertThatWrapper object at 0x7fde39755ac8>
time_travel = <appdaemontestframework.time_travel.TimeTravelWrapper object at 0x7fde39755b38>
def test_demo(given_that, motion_light, assert_that, time_travel):
given_that.state_of('light.test_light').is_set_to('on')
time_travel.assert_current_time(0).seconds()
motion_light.motion(DELAY)
time_travel.fast_forward(DELAY).seconds()
time_travel.assert_current_time(DELAY).seconds()
> assert_that('light.test_light').was.turned_off()
tests/test_motion_lights.py:26:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <appdaemontestframework.assert_that.WasWrapper object at 0x7fde39755ba8>
def turned_off(self):
""" Assert that a given entity_id has been turned off """
entity_id = self.thing_to_check
service_not_called = _capture_assert_failure_exception(
lambda: self.hass_functions['call_service'].assert_any_call(
ServiceOnAnyDomain('turn_off'),
entity_id=entity_id))
turn_off_helper_not_called = _capture_assert_failure_exception(
lambda: self.hass_functions['turn_off'].assert_any_call(
entity_id))
if service_not_called and turn_off_helper_not_called:
raise EitherOrAssertionError(
> service_not_called, turn_off_helper_not_called)
E appdaemontestframework.assert_that.EitherOrAssertionError:
E
E At least ONE of the following exceptions should not have been raised
E
E The problem is EITHER:
E call_service('ANY_DOMAIN/turn_off', entity_id='light.test_light') call not found
E
E OR
E turn_off('light.test_light') call not found
/usr/local/lib/python3.6/site-packages/appdaemontestframework/assert_that.py:106: EitherOrAssertionError
=========================== 1 failed in 0.08 seconds ===========================
Sorry. just got a demo test to work and found a defect in my appdaemon script. Automated testing is already helping me out a lot :) Awesome work on this framework man. Closing!
I'm so happy to see this project is helping other people. It's really just a thing that made my life easier and thought I'd share . . . . but didn't expect anyone to actually use it ๐
So yeah it's really cool! Thank you for the encouragement ๐
On a side note:
cancel_timer
isn't supported. As a matter of fact, time_travel
only works with the run_in
function.
That might change in the future . . . or might not. No commitement so far, I'll see if I need it enough to spend the time on it ๐
Created parameterised test cases with it. Totalling 81 test cases so far. All pass <3
I'm migrating to a state machine based implementation for my app. These automated tests will help me ensure the appdaemon app continues to do what it does (despite a very different implementation inside).
What else is this framework missing that you're aware of?
A state machine, nice! Were you inspired by my smart bathroom? ^_^
Regarding your question, basically, any function not patched in init_framework.py - L20 is not supported.
Adding a function is a pretty straightforward process, the function would then be available in the hass_functions
fixture. You can add a pull request any-time. But leveraging that function in a way that's seamless in one of the higher level fixtures like assert_that
, given_that
and time_travel
requires a bit more thinking. You are welcome as well to submit a pull request for modifying these, but then we'd have a conversation to see if that makes sense, and if not, find a way to tweak it in a way that does ;)
Oh and just thought of a tip for your state machine. As you can see in my example, implementing a "regular" / "in-memory" state machine works well, but testing can get a bit cumbersome (to get in the given state at the beginning of each test). On another project, I found a solution that makes everything so much more seamless: Externalize the current state in an input_select
stored in HomeAssistant. Basically taking the functional implementation of a state-machine and using Home Assistant as a database. Not only, does it makes testing incredibly easier, it also provides a live view of the inner state of each room, as well as their history, etc. All for free. It's really cool :)
But then again, it's just a tip ;)