Initial commit
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
98
tests/test_dummy.py
Normal file
98
tests/test_dummy.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import pytest
|
||||
|
||||
from myutils.Dummy import Dummy
|
||||
|
||||
|
||||
def test_i_can_call_any_method():
|
||||
"""Test that calling any non-existent method does not raise an error."""
|
||||
dummy = Dummy()
|
||||
dummy.any_method()
|
||||
dummy.some_random_function()
|
||||
dummy.whatever_i_want()
|
||||
|
||||
|
||||
def test_i_can_call_method_with_arguments():
|
||||
"""Test that methods can be called with positional and keyword arguments."""
|
||||
dummy = Dummy()
|
||||
dummy.method_with_args(1, 2, 3)
|
||||
dummy.method_with_kwargs(foo="bar", baz=123)
|
||||
dummy.method_with_both(1, 2, key="value", another=True)
|
||||
|
||||
|
||||
def test_i_can_access_any_attribute():
|
||||
"""Test that accessing any non-existent attribute returns a Dummy instance."""
|
||||
dummy = Dummy()
|
||||
result = dummy.any_attribute
|
||||
assert isinstance(result, Dummy)
|
||||
|
||||
result2 = dummy.another_property
|
||||
assert isinstance(result2, Dummy)
|
||||
|
||||
|
||||
def test_i_can_chain_attributes():
|
||||
"""Test that chaining multiple attributes works correctly."""
|
||||
dummy = Dummy()
|
||||
result = dummy.foo.bar.baz
|
||||
assert isinstance(result, Dummy)
|
||||
|
||||
result2 = dummy.level1.level2.level3.level4
|
||||
assert isinstance(result2, Dummy)
|
||||
|
||||
|
||||
def test_i_can_set_any_attribute():
|
||||
"""Test that setting any attribute does not raise an error."""
|
||||
dummy = Dummy()
|
||||
dummy.property = "value"
|
||||
dummy.number = 42
|
||||
dummy.nested = {"key": "value"}
|
||||
|
||||
|
||||
def test_method_call_returns_none():
|
||||
"""Test that calling a method returns None (no chaining)."""
|
||||
dummy = Dummy()
|
||||
result = dummy.some_method()
|
||||
assert result is None
|
||||
|
||||
result2 = dummy.another_method(1, 2, 3)
|
||||
assert result2 is None
|
||||
|
||||
|
||||
def test_dummy_evaluates_to_false():
|
||||
"""Test that Dummy evaluates to False in boolean context."""
|
||||
dummy = Dummy()
|
||||
assert not dummy
|
||||
assert bool(dummy) is False
|
||||
|
||||
if dummy:
|
||||
pytest.fail("Dummy should evaluate to False")
|
||||
|
||||
|
||||
def test_dummy_repr():
|
||||
"""Test the string representation of Dummy object."""
|
||||
dummy = Dummy()
|
||||
assert repr(dummy) == "<Dummy>"
|
||||
assert str(dummy) == "<Dummy>"
|
||||
|
||||
|
||||
def test_attribute_is_not_stored():
|
||||
"""Test that assigned attributes are not actually stored."""
|
||||
dummy = Dummy()
|
||||
dummy.stored_value = 123
|
||||
|
||||
# Accessing the attribute should return a new Dummy, not the stored value
|
||||
result = dummy.stored_value
|
||||
assert isinstance(result, Dummy)
|
||||
assert result is not 123
|
||||
|
||||
|
||||
def test_i_can_chain_and_call():
|
||||
"""Test that chaining attributes and then calling works correctly."""
|
||||
dummy = Dummy()
|
||||
result = dummy.foo.bar()
|
||||
assert result is None
|
||||
|
||||
result2 = dummy.level1.level2.level3()
|
||||
assert result2 is None
|
||||
|
||||
result3 = dummy.attr1.attr2.method(1, 2, key="value")
|
||||
assert result3 is None
|
||||
75
tests/test_expando.py
Normal file
75
tests/test_expando.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import pytest
|
||||
|
||||
from myutils.Expando import Expando
|
||||
|
||||
|
||||
def test_i_can_get_properties():
|
||||
props = {"a": 10,
|
||||
"b": {
|
||||
"c": "value",
|
||||
"d": 20
|
||||
}}
|
||||
dynamic = Expando(props)
|
||||
|
||||
assert dynamic.a == 10
|
||||
assert dynamic.b.c == "value"
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
assert dynamic.unknown == "some_value"
|
||||
|
||||
|
||||
def test_i_can_get():
|
||||
props = {"a": 10,
|
||||
"b": {
|
||||
"c": "value",
|
||||
"d": 20
|
||||
}}
|
||||
dynamic = Expando(props)
|
||||
|
||||
assert dynamic.get("a") == 10
|
||||
assert dynamic.get("b.c") == "value"
|
||||
assert dynamic.get("unknown") is None
|
||||
|
||||
|
||||
def test_i_can_get_from_list():
|
||||
props = {"a": [{"c": "value1", "d": 1}, {"c": "value2", "d": 2}]}
|
||||
dynamic = Expando(props)
|
||||
|
||||
assert dynamic.get("a.c") == ["value1", "value2"]
|
||||
|
||||
|
||||
def test_none_is_returned_when_get_from_list_and_property_does_not_exist():
|
||||
props = {"a": [{"c": "value1", "d": 1},
|
||||
{"a": "value2", "d": 2} # 'c' does not exist in the second row
|
||||
]}
|
||||
dynamic = Expando(props)
|
||||
|
||||
assert dynamic.get("a.c") == ["value1"]
|
||||
|
||||
|
||||
def test_i_can_manage_none_values():
|
||||
props = {"a": 10,
|
||||
"b": None}
|
||||
dynamic = Expando(props)
|
||||
|
||||
assert dynamic.get("b.c") is None
|
||||
|
||||
|
||||
def test_i_can_manage_none_values_in_list():
|
||||
props = {"a": [{"b": {"c": "value"}},
|
||||
{"b": None}
|
||||
]}
|
||||
dynamic = Expando(props)
|
||||
|
||||
assert dynamic.get("a.b.c") == ["value"]
|
||||
|
||||
|
||||
def test_i_can_add_new_properties():
|
||||
props = {"a": 10,
|
||||
"b": 20}
|
||||
dynamic = Expando(props)
|
||||
dynamic["c"] = 30
|
||||
|
||||
assert dynamic.a == 10
|
||||
assert dynamic.b == 20
|
||||
assert dynamic.c == 30
|
||||
173
tests/test_observable.py
Normal file
173
tests/test_observable.py
Normal file
@@ -0,0 +1,173 @@
|
||||
import pytest
|
||||
|
||||
from myutils.observable import NotObservableError, make_observable, bind
|
||||
|
||||
|
||||
# Test fixtures
|
||||
class Demo:
|
||||
def __init__(self):
|
||||
self.number = 1
|
||||
|
||||
def get_double(self):
|
||||
return self.number * 2
|
||||
|
||||
|
||||
# Tests
|
||||
def test_i_can_make_an_object_observable():
|
||||
demo = Demo()
|
||||
result = make_observable(demo)
|
||||
|
||||
assert hasattr(demo, '_listeners')
|
||||
assert isinstance(demo._listeners, dict)
|
||||
assert result is demo
|
||||
|
||||
|
||||
def test_i_can_bind_a_callback_to_an_attribute():
|
||||
demo = Demo()
|
||||
make_observable(demo)
|
||||
|
||||
callback = lambda old, new: None
|
||||
bind(demo, 'number', callback)
|
||||
|
||||
assert 'number' in demo._listeners
|
||||
assert callback in demo._listeners['number']
|
||||
|
||||
|
||||
def test_i_can_receive_notification_when_attribute_changes():
|
||||
demo = Demo()
|
||||
make_observable(demo)
|
||||
|
||||
called = []
|
||||
bind(demo, 'number', lambda old, new: called.append(True))
|
||||
|
||||
demo.number = 5
|
||||
|
||||
assert len(called) == 1
|
||||
|
||||
|
||||
def test_i_can_receive_old_and_new_values_in_callback():
|
||||
demo = Demo()
|
||||
make_observable(demo)
|
||||
|
||||
values = []
|
||||
bind(demo, 'number', lambda old, new: values.append((old, new)))
|
||||
|
||||
demo.number = 5
|
||||
demo.number = 10
|
||||
|
||||
assert values == [(1, 5), (5, 10)]
|
||||
|
||||
|
||||
def test_i_can_bind_multiple_callbacks_to_same_attribute():
|
||||
demo = Demo()
|
||||
make_observable(demo)
|
||||
|
||||
calls1 = []
|
||||
calls2 = []
|
||||
bind(demo, 'number', lambda old, new: calls1.append(new))
|
||||
bind(demo, 'number', lambda old, new: calls2.append(new))
|
||||
|
||||
demo.number = 5
|
||||
|
||||
assert calls1 == [5]
|
||||
assert calls2 == [5]
|
||||
|
||||
|
||||
def test_i_can_bind_callbacks_to_different_attributes():
|
||||
demo = Demo()
|
||||
make_observable(demo)
|
||||
|
||||
number_calls = []
|
||||
name_calls = []
|
||||
bind(demo, 'number', lambda old, new: number_calls.append(new))
|
||||
bind(demo, 'name', lambda old, new: name_calls.append(new))
|
||||
|
||||
demo.number = 5
|
||||
demo.name = "test"
|
||||
|
||||
assert number_calls == [5]
|
||||
assert name_calls == ["test"]
|
||||
|
||||
|
||||
def test_i_can_modify_non_observed_attributes_without_notification():
|
||||
demo = Demo()
|
||||
make_observable(demo)
|
||||
|
||||
called = []
|
||||
bind(demo, 'number', lambda old, new: called.append(True))
|
||||
|
||||
demo.other_attr = "value"
|
||||
|
||||
assert len(called) == 0
|
||||
assert demo.other_attr == "value"
|
||||
|
||||
|
||||
def test_i_can_have_multiple_instances_with_independent_observers():
|
||||
demo1 = Demo()
|
||||
demo2 = Demo()
|
||||
|
||||
make_observable(demo1)
|
||||
|
||||
calls1 = []
|
||||
bind(demo1, 'number', lambda old, new: calls1.append(new))
|
||||
|
||||
demo1.number = 5
|
||||
demo2.number = 10
|
||||
|
||||
# Only demo1 should trigger callback
|
||||
assert calls1 == [5]
|
||||
assert demo1.number == 5
|
||||
assert demo2.number == 10
|
||||
|
||||
|
||||
def test_i_can_call_make_observable_multiple_times_safely():
|
||||
demo = Demo()
|
||||
|
||||
result1 = make_observable(demo)
|
||||
result2 = make_observable(demo)
|
||||
|
||||
assert result1 is result2
|
||||
assert hasattr(demo, '_listeners')
|
||||
|
||||
# Should still work normally
|
||||
called = []
|
||||
bind(demo, 'number', lambda old, new: called.append(new))
|
||||
demo.number = 5
|
||||
|
||||
assert len(called) == 1
|
||||
|
||||
|
||||
def test_i_cannot_bind_before_making_observable():
|
||||
demo = Demo()
|
||||
|
||||
with pytest.raises(NotObservableError) as exc_info:
|
||||
bind(demo, 'number', lambda old, new: None)
|
||||
|
||||
assert "must be made observable" in str(exc_info.value).lower()
|
||||
|
||||
|
||||
def test_i_can_set_attribute_that_does_not_exist_yet():
|
||||
demo = Demo()
|
||||
make_observable(demo)
|
||||
|
||||
values = []
|
||||
bind(demo, 'new_attr', lambda old, new: values.append((old, new)))
|
||||
|
||||
demo.new_attr = "first_value"
|
||||
|
||||
assert values == [(None, "first_value")]
|
||||
assert demo.new_attr == "first_value"
|
||||
|
||||
|
||||
def test_i_can_preserve_original_class_behavior():
|
||||
demo = Demo()
|
||||
make_observable(demo)
|
||||
|
||||
# Test that original methods still work
|
||||
assert demo.get_double() == 2
|
||||
|
||||
demo.number = 5
|
||||
assert demo.get_double() == 10
|
||||
|
||||
# Test that isinstance still works
|
||||
assert isinstance(demo, Demo)
|
||||
Reference in New Issue
Block a user