Skip to content

Commit

Permalink
Add support for directly giving a property a side-effect (#112)
Browse files Browse the repository at this point in the history
* Add support for directly giving a property a side-effect

* Fix not awaited warning in test
  • Loading branch information
JamesHutchison authored Nov 26, 2023
1 parent 37a6ccf commit a1e0de1
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 7 deletions.
14 changes: 14 additions & 0 deletions megamock/megapatches.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ def it(
autostart: bool = True,
mocker: ModuleType | object | None = None,
new_callable: Callable | None = None,
side_effect: Any | None = None,
**kwargs: Any,
):
"""
Expand All @@ -293,6 +294,7 @@ def it(
object to return. This is usually some replacement Mock bject.
This is mainly for legacy support and is not recommended since it can't be
combined with autospec.
:param side_effect: The side-effect to use
"""
if mocker is None:
mocker = MegaPatch.default_mocker
Expand All @@ -315,6 +317,9 @@ def it(
else:
parent_mock = None

if side_effect is not None:
kwargs["side_effect"] = side_effect # this may get popped later

if behavior is None:
behavior = MegaPatchBehavior.for_thing(thing)
if new_callable is not None:
Expand All @@ -323,6 +328,15 @@ def it(
return_value = kwargs.get("return_value")
kwargs["new_callable"] = new_callable
else:
# support giving properties side-effects
if (
isinstance(thing, (property, cached_property))
and side_effect
and not new
):
new = property(MegaMock(side_effect=side_effect))
kwargs.pop("side_effect")

if (autospec := kwargs.pop("autospec", None)) in (True, False):
behavior.autospec = autospec
new, return_value = MegaPatch._new_return_value(
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/test_megamocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,9 +390,10 @@ class TestAsyncMock:
async def test_async_mock_basics(self) -> None:
mega_mock: AsyncMegaMock = AsyncMegaMock()
assert asyncio.iscoroutinefunction(mega_mock) is True
assert inspect.isawaitable(mega_mock()) is True
mock_coroutine = mega_mock()
assert inspect.isawaitable(mock_coroutine) is True

await mega_mock()
await mock_coroutine
assert mega_mock.await_count == 1

async def test_function_side_effect(self) -> None:
Expand Down
13 changes: 8 additions & 5 deletions tests/unit/test_megapatches.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,12 +234,15 @@ def test_setting_side_effect(self) -> None:

assert str(exc.value) == "Error!"

@pytest.mark.xfail
def test_setting_side_effect_to_a_property(self) -> None:
# This doesn't work because side_effect requires making a call.
# If you really need to have a property that raises an exception,
# then you'll need to structure your code to call a function
# and then mock that function.
MegaPatch.it(Foo.zzz, side_effect=Exception("Error!"))

with pytest.raises(Exception) as exc:
Foo("s").zzz

assert str(exc.value) == "Error!"

def test_setting_side_effect_to_a_cached_property(self) -> None:
MegaPatch.it(Foo.helpful_manager, side_effect=Exception("Error!"))

with pytest.raises(Exception) as exc:
Expand Down

0 comments on commit a1e0de1

Please sign in to comment.