diff --git a/decoy/core.py b/decoy/core.py index 43256ee..375cbbe 100644 --- a/decoy/core.py +++ b/decoy/core.py @@ -3,7 +3,7 @@ from .call_handler import CallHandler from .spy import SpyCreator -from .spy_events import WhenRehearsal, PropAccessType, SpyEvent, SpyPropAccess +from .spy_events import WhenRehearsal, PropAccessType, SpyEvent, SpyInfo, SpyPropAccess from .spy_log import SpyLog from .stub_store import StubBehavior, StubStore from .types import ContextValueT, ReturnT @@ -65,20 +65,15 @@ def verify( count=len(_rehearsals), ignore_extra_args=ignore_extra_args, ) - calls = self._spy_log.get_calls_to_verify([r.spy_id for r in rehearsals]) + calls = self._spy_log.get_calls_to_verify([r.spy.id for r in rehearsals]) self._verifier.verify(rehearsals=rehearsals, calls=calls, times=times) def prop(self, _rehearsal: ReturnT) -> "PropCore": """Get a property setter/deleter rehearser.""" - spy_id, spy_name, payload = self._spy_log.consume_prop_rehearsal() + spy, payload = self._spy_log.consume_prop_rehearsal() - return PropCore( - spy_id=spy_id, - spy_name=spy_name, - prop_name=payload.prop_name, - spy_log=self._spy_log, - ) + return PropCore(spy=spy, prop_name=payload.prop_name, spy_log=self._spy_log) def reset(self) -> None: """Reset and remove all stored spies and stubs.""" @@ -134,21 +129,18 @@ class PropCore: def __init__( self, - spy_id: int, - spy_name: str, + spy: SpyInfo, prop_name: str, spy_log: SpyLog, ) -> None: - self._spy_id = spy_id - self._spy_name = spy_name + self._spy = spy self._prop_name = prop_name self._spy_log = spy_log def set(self, value: Any) -> None: """Create a property setter rehearsal.""" event = SpyEvent( - spy_id=self._spy_id, - spy_name=self._spy_name, + spy=self._spy, payload=SpyPropAccess( prop_name=self._prop_name, access_type=PropAccessType.SET, @@ -160,8 +152,7 @@ def set(self, value: Any) -> None: def delete(self) -> None: """Create a property deleter rehearsal.""" event = SpyEvent( - spy_id=self._spy_id, - spy_name=self._spy_name, + spy=self._spy, payload=SpyPropAccess( prop_name=self._prop_name, access_type=PropAccessType.DELETE, diff --git a/decoy/spec.py b/decoy/spec.py deleted file mode 100644 index 7900db9..0000000 --- a/decoy/spec.py +++ /dev/null @@ -1,167 +0,0 @@ -"""Mock specification.""" -import inspect -import functools -import warnings -from typing import Any, Dict, NamedTuple, Optional, Tuple, Type, Union, get_type_hints - -from .warnings import IncorrectCallWarning - - -class BoundArgs(NamedTuple): - """Arguments bound to a spec.""" - - args: Tuple[Any, ...] - kwargs: Dict[str, Any] - - -class Spec: - """Interface defining a Spy's specification. - - Arguments: - source: The source object for the specification. - name: The spec's name. If left unspecified, will be derived from - `source`, if possible. Will fallback to a default value. - module_name: The spec's module name. If left unspecified or `True`, - will be derived from `source`, if possible. If explicitly set to `None` - or `False` or it is unable to be derived, a module name will not be used. - - """ - - _DEFAULT_SPY_NAME = "unnamed" - - def __init__( - self, - source: Optional[Any], - name: Optional[str], - module_name: Union[str, bool, None] = True, - ) -> None: - self._source = source - - if name is not None: - self._name = name - elif source is not None: - self._name = getattr(source, "__name__", self._DEFAULT_SPY_NAME) - else: - self._name = self._DEFAULT_SPY_NAME - - if isinstance(module_name, str): - self._module_name: Optional[str] = module_name - elif module_name is True and source is not None: - self._module_name = getattr(source, "__module__", None) - else: - self._module_name = None - - def get_name(self) -> str: - """Get the Spec's human readable name. - - Name may be manually specified or derived from the object the Spec - represents. - """ - return self._name - - def get_full_name(self) -> str: - """Get the full name of the spec. - - Full name includes the module name of the object the Spec represents, - if available. - """ - name = self._name - module_name = self._module_name - return f"{module_name}.{name}" if module_name else name - - def get_signature(self) -> Optional[inspect.Signature]: - """Get the Spec's signature, if Spec represents a callable.""" - source = self._get_source() - - try: - return inspect.signature(source, follow_wrapped=True) - except (ValueError, TypeError): - return None - - def get_class_type(self) -> Optional[Type[Any]]: - """Get the Spec's class type, if Spec represents a class.""" - return self._source if inspect.isclass(self._source) else None - - def get_is_async(self) -> bool: - """Get whether the Spec represents an async. callable.""" - source = self._get_source() - - # `iscoroutinefunction` does not work for `partial` on Python < 3.8 - if isinstance(source, functools.partial): - source = source.func - - return inspect.iscoroutinefunction(source) - - def bind_args(self, *args: Any, **kwargs: Any) -> BoundArgs: - """Bind given args and kwargs to the Spec's signature, if possible. - - If no signature or unable to bind, will simply pass args and kwargs - through without modification. - """ - signature = self.get_signature() - - if signature: - try: - bound_args = signature.bind(*args, **kwargs) - except TypeError as e: - # stacklevel: 4 ensures warning is linked to call location - warnings.warn(IncorrectCallWarning(e), stacklevel=4) - else: - args = bound_args.args - kwargs = bound_args.kwargs - - return BoundArgs(args=args, kwargs=kwargs) - - def get_child_spec(self, name: str) -> "Spec": - """Get a child attribute, property, or method's Spec from this Spec.""" - source = self._source - child_name = f"{self._name}.{name}" - child_source = None - - if inspect.isclass(source): - # use type hints to get child spec for class attributes - child_hint = _get_type_hints(source).get(name) - # use inspect to get child spec for methods and properties - child_source = inspect.getattr_static(source, name, child_hint) - - if isinstance(child_source, property): - child_source = _get_type_hints(child_source.fget).get("return") - - elif isinstance(child_source, staticmethod): - child_source = child_source.__func__ - - else: - child_source = inspect.unwrap(child_source) - - if inspect.isfunction(child_source): - # consume the `self` argument of the method to ensure proper - # signature reporting by wrapping it in a partial - child_source = functools.partial(child_source, None) - - return Spec(source=child_source, name=child_name, module_name=self._module_name) - - def _get_source(self) -> Any: - source = self._source - - # check if spec source is a class with a __call__ method - if inspect.isclass(source): - call_method = inspect.getattr_static(source, "__call__", None) - if inspect.isfunction(call_method): - # consume the `self` argument of the method to ensure proper - # signature reporting by wrapping it in a partial - source = functools.partial(call_method, None) - - return source - - -def _get_type_hints(obj: Any) -> Dict[str, Any]: - """Get type hints for an object, if possible. - - The builtin `typing.get_type_hints` may fail at runtime, - e.g. if a type is subscriptable according to mypy but not - according to Python. - """ - try: - return get_type_hints(obj) - except Exception: - return {} diff --git a/decoy/spy.py b/decoy/spy.py index b6ac7a8..1efc4ff 100644 --- a/decoy/spy.py +++ b/decoy/spy.py @@ -4,10 +4,10 @@ [unittest.mock library](https://docs.python.org/3/library/unittest.mock.html). """ from types import TracebackType -from typing import Any, ContextManager, Dict, Optional, Type, Union, cast +from typing import Any, ContextManager, Dict, Optional, Type, Union, cast, overload from .call_handler import CallHandler -from .spec import Spec +from .spy_core import SpyCore from .spy_events import SpyCall, SpyEvent, SpyPropAccess, PropAccessType @@ -18,7 +18,7 @@ class BaseSpy(ContextManager[Any]): - Lazily constructs child spies when an attribute is accessed """ - _spec: Spec + _core: SpyCore _call_handler: CallHandler _spy_creator: "SpyCreator" _spy_children: Dict[str, "BaseSpy"] @@ -26,22 +26,22 @@ class BaseSpy(ContextManager[Any]): def __init__( self, - spec: Spec, + core: SpyCore, call_handler: CallHandler, spy_creator: "SpyCreator", ) -> None: """Initialize a BaseSpy from a call handler and an optional spec object.""" - super().__setattr__("_spec", spec) + super().__setattr__("_core", core) super().__setattr__("_call_handler", call_handler) super().__setattr__("_spy_creator", spy_creator) super().__setattr__("_spy_children", {}) super().__setattr__("_spy_property_values", {}) - super().__setattr__("__signature__", self._spec.get_signature()) + super().__setattr__("__signature__", self._core.signature) @property # type: ignore[misc] def __class__(self) -> Any: """Ensure Spy can pass `instanceof` checks.""" - return self._spec.get_class_type() or type(self) + return self._core.class_type or type(self) def __enter__(self) -> Any: """Allow a spy to be used as a context manager.""" @@ -75,7 +75,7 @@ async def __aexit__( def __repr__(self) -> str: """Get a helpful string representation of the spy.""" - return f"" + return f"" def __getattr__(self, name: str) -> Any: """Get a property of the spy, always returning a child spy.""" @@ -88,8 +88,7 @@ def __getattr__(self, name: str) -> Any: def __setattr__(self, name: str, value: Any) -> None: """Set a property on the spy, recording the call.""" event = SpyEvent( - spy_id=id(self), - spy_name=self._spec.get_name(), + spy=self._core.info, payload=SpyPropAccess( prop_name=name, access_type=PropAccessType.SET, @@ -102,8 +101,7 @@ def __setattr__(self, name: str, value: Any) -> None: def __delattr__(self, name: str) -> None: """Delete a property on the spy, recording the call.""" event = SpyEvent( - spy_id=id(self), - spy_name=self._spec.get_name(), + spy=self._core.info, payload=SpyPropAccess(prop_name=name, access_type=PropAccessType.DELETE), ) self._call_handler.handle(event) @@ -114,8 +112,7 @@ def _get_or_create_child_spy(self, name: str, child_is_async: bool = False) -> A # check for any stubbed behaviors for property getter get_result = self._call_handler.handle( SpyEvent( - spy_id=id(self), - spy_name=self._spec.get_name(), + spy=self._core.info, payload=SpyPropAccess( prop_name=name, access_type=PropAccessType.GET, @@ -133,17 +130,16 @@ def _get_or_create_child_spy(self, name: str, child_is_async: bool = False) -> A if name in self._spy_children: return self._spy_children[name] - child_spec = self._spec.get_child_spec(name) - child_spy = self._spy_creator.create(spec=child_spec, is_async=child_is_async) + child_core = self._core.create_child_core(name=name, is_async=child_is_async) + child_spy = self._spy_creator.create(core=child_core) self._spy_children[name] = child_spy return child_spy def _call(self, *args: Any, **kwargs: Any) -> Any: - bound_args, bound_kwargs = self._spec.bind_args(*args, **kwargs) + bound_args, bound_kwargs = self._core.bind_args(*args, **kwargs) call = SpyEvent( - spy_id=id(self), - spy_name=self._spec.get_name(), + spy=self._core.info, payload=SpyCall( args=bound_args, kwargs=bound_kwargs, @@ -154,6 +150,14 @@ def _call(self, *args: Any, **kwargs: Any) -> Any: return result.value if result else None +class AsyncSpy(BaseSpy): + """An object that records all async. calls made to itself and its children.""" + + async def __call__(self, *args: Any, **kwargs: Any) -> Any: + """Handle a call to the spy asynchronously.""" + return self._call(*args, **kwargs) + + class Spy(BaseSpy): """An object that records all calls made to itself and its children.""" @@ -162,12 +166,7 @@ def __call__(self, *args: Any, **kwargs: Any) -> Any: return self._call(*args, **kwargs) -class AsyncSpy(BaseSpy): - """An object that records all async. calls made to itself and its children.""" - - async def __call__(self, *args: Any, **kwargs: Any) -> Any: - """Handle a call to the spy asynchronously.""" - return self._call(*args, **kwargs) +AnySpy = Union[AsyncSpy, Spy] class SpyCreator: @@ -176,25 +175,36 @@ class SpyCreator: def __init__(self, call_handler: CallHandler) -> None: self._call_handler = call_handler + @overload + def create(self, *, core: SpyCore) -> AnySpy: + ... + + @overload + def create( + self, *, spec: Optional[object], name: Optional[str], is_async: bool + ) -> AnySpy: + ... + def create( self, + *, + core: Optional[SpyCore] = None, spec: Optional[object] = None, name: Optional[str] = None, is_async: bool = False, - ) -> Union[AsyncSpy, Spy]: + ) -> AnySpy: """Create a Spy from a spec. Functions and classes passed to `spec` will be inspected (and have any type annotations inspected) to ensure `AsyncSpy`'s are returned where necessary. """ - if not isinstance(spec, Spec): - spec = Spec(source=spec, name=name) + if not isinstance(core, SpyCore): + core = SpyCore(source=spec, name=name, is_async=is_async) - is_async = is_async or spec.get_is_async() - spy_cls: Union[Type[AsyncSpy], Type[Spy]] = AsyncSpy if is_async else Spy + spy_cls: Type[AnySpy] = AsyncSpy if core.is_async else Spy return spy_cls( - spec=spec, + core=core, spy_creator=self, call_handler=self._call_handler, ) diff --git a/decoy/spy_core.py b/decoy/spy_core.py new file mode 100644 index 0000000..df61205 --- /dev/null +++ b/decoy/spy_core.py @@ -0,0 +1,198 @@ +"""Mock specification.""" +import inspect +import functools +import warnings +from typing import Any, Dict, NamedTuple, Optional, Tuple, Type, Union, get_type_hints + +from .spy_events import SpyInfo +from .warnings import IncorrectCallWarning + + +class _FROM_SOURCE: + pass + + +FROM_SOURCE = _FROM_SOURCE() +"""Indicates a value that should be derived from the source object.""" + + +class BoundArgs(NamedTuple): + """Arguments bound to a spec.""" + + args: Tuple[Any, ...] + kwargs: Dict[str, Any] + + +_DEFAULT_SPY_NAME = "unnamed" + + +class SpyCore: + """Spy configuration values. + + Arguments: + source: The source object the Spy is mimicing. + name: The spec's name. If `None`, will be derived from `source`. + Will fallback to a default value. + module_name: The spec's module name. If left unspecified, + will be derived from `source`. If explicitly set to `None`, + a module name will not be used. + is_async: If the spy should be configured to be an asynchronous callable. + If `False` or unspecified, will derive from `source`. + """ + + def __init__( + self, + source: Optional[object], + name: Optional[str], + module_name: Union[str, _FROM_SOURCE, None] = FROM_SOURCE, + is_async: bool = False, + ) -> None: + self._source = source + self._name = _get_name(source) if name is None else name + self._module_name = ( + _get_module_name(source) if module_name is FROM_SOURCE else module_name + ) + self._full_name = ( + f"{self._module_name}.{self._name}" if self._module_name else self._name + ) + self._info = SpyInfo(id=id(self), name=self._name) + self._class_type = self._source if inspect.isclass(self._source) else None + self._signature = _get_signature(source) + self._is_async = is_async or _get_is_async(source) + + @property + def info(self) -> SpyInfo: + """Get the spy's information object, for use in SpyLog events.""" + return self._info + + @property + def full_name(self) -> str: + """Get the full name of the spy, including module name, if available.""" + return self._full_name + + @property + def signature(self) -> Optional[inspect.Signature]: + """Get the spy's signature, if spy represents a callable.""" + return self._signature + + @property + def class_type(self) -> Optional[Type[Any]]: + """Get the spy's class type, if spy represents a class.""" + return self._class_type + + @property + def is_async(self) -> bool: + """Get whether the spy represents an asynchronous callable.""" + return self._is_async + + def bind_args(self, *args: Any, **kwargs: Any) -> BoundArgs: + """Bind given args and kwargs to the Spec's signature, if possible. + + If no signature or unable to bind, will simply pass args and kwargs + through without modification. + """ + signature = self._signature + + if signature: + try: + bound_args = signature.bind(*args, **kwargs) + except TypeError as e: + # stacklevel: 4 ensures warning is linked to call location + warnings.warn(IncorrectCallWarning(e), stacklevel=4) + else: + args = bound_args.args + kwargs = bound_args.kwargs + + return BoundArgs(args=args, kwargs=kwargs) + + def create_child_core(self, name: str, is_async: bool) -> "SpyCore": + """Create a child attribute, property, or method's SpyCore.""" + source = self._source + child_name = f"{self._name}.{name}" + child_source = None + + if inspect.isclass(source): + # use type hints to get child spec for class attributes + child_hint = _get_type_hints(source).get(name) + # use inspect to get child spec for methods and properties + child_source = inspect.getattr_static(source, name, child_hint) + + if isinstance(child_source, property): + child_source = _get_type_hints(child_source.fget).get("return") + + elif isinstance(child_source, staticmethod): + child_source = child_source.__func__ + + else: + child_source = inspect.unwrap(child_source) + + if inspect.isfunction(child_source): + # consume the `self` argument of the method to ensure proper + # signature reporting by wrapping it in a partial + child_source = functools.partial(child_source, None) + + return SpyCore( + source=child_source, + name=child_name, + module_name=self._module_name, + is_async=is_async, + ) + + +def _get_name(source: Any) -> str: + """Get the name of a source object.""" + source_name = getattr(source, "__name__", None) if source is not None else None + return source_name if isinstance(source_name, str) else _DEFAULT_SPY_NAME + + +def _get_module_name(source: Any) -> Optional[str]: + """Get the name of a source object.""" + module_name = getattr(source, "__module__", None) if source is not None else None + return module_name if isinstance(module_name, str) else None + + +def _get_signature(source: Any) -> Optional[inspect.Signature]: + """Get the signature of a source object.""" + source = _get_callable_source(source) + + try: + return inspect.signature(source, follow_wrapped=True) + except (ValueError, TypeError): + return None + + +def _get_is_async(source: Any) -> bool: + """Get whether the source is an asynchronous callable.""" + source = _get_callable_source(source) + + # `iscoroutinefunction` does not work for `partial` on Python < 3.8 + if isinstance(source, functools.partial): + source = source.func + + return inspect.iscoroutinefunction(source) + + +def _get_callable_source(source: Any) -> Any: + """Return the source's callable, checking if a class has a __call__ method.""" + # check if spec source is a class with a __call__ method + if inspect.isclass(source): + call_method = inspect.getattr_static(source, "__call__", None) + if inspect.isfunction(call_method): + # consume the `self` argument of the method to ensure proper + # signature reporting by wrapping it in a partial + source = functools.partial(call_method, None) + + return source + + +def _get_type_hints(obj: Any) -> Dict[str, Any]: + """Get type hints for an object, if possible. + + The builtin `typing.get_type_hints` may fail at runtime, + e.g. if a type is subscriptable according to mypy but not + according to Python. + """ + try: + return get_type_hints(obj) + except Exception: + return {} diff --git a/decoy/spy_events.py b/decoy/spy_events.py index 112df88..0e272be 100644 --- a/decoy/spy_events.py +++ b/decoy/spy_events.py @@ -11,6 +11,13 @@ class PropAccessType(str, enum.Enum): DELETE = "delete" +class SpyInfo(NamedTuple): + """Spy information and configuration.""" + + id: int + name: str + + class SpyCall(NamedTuple): """Spy event payload representing a call to a function or method.""" @@ -30,32 +37,28 @@ class SpyPropAccess(NamedTuple): class SpyEvent(NamedTuple): """An interaction with a spy by the code under test.""" - spy_id: int - spy_name: str + spy: SpyInfo payload: Union[SpyCall, SpyPropAccess] class WhenRehearsal(NamedTuple): """A spy interaction that has been used as a rehearsal for `when`.""" - spy_id: int - spy_name: str + spy: SpyInfo payload: Union[SpyCall, SpyPropAccess] class VerifyRehearsal(NamedTuple): """A spy interaction that has been used as a rehearsal for `verify`.""" - spy_id: int - spy_name: str + spy: SpyInfo payload: Union[SpyCall, SpyPropAccess] class PropRehearsal(NamedTuple): """A spy interaction that has been used as a rehearsal for `prop`.""" - spy_id: int - spy_name: str + spy: SpyInfo payload: SpyPropAccess @@ -66,7 +69,7 @@ class PropRehearsal(NamedTuple): def match_event(event: AnySpyEvent, rehearsal: SpyRehearsal) -> bool: """Check if a call matches a given rehearsal.""" - if event.spy_id != rehearsal.spy_id: + if event.spy != rehearsal.spy: return False if ( diff --git a/decoy/spy_log.py b/decoy/spy_log.py index c649268..06fe545 100644 --- a/decoy/spy_log.py +++ b/decoy/spy_log.py @@ -36,9 +36,9 @@ def consume_when_rehearsal(self, ignore_extra_args: bool) -> WhenRehearsal: if not isinstance(event, SpyEvent): raise MissingRehearsalError() - spy_id, spy_name, payload = _apply_ignore_extra_args(event, ignore_extra_args) + spy, payload = _apply_ignore_extra_args(event, ignore_extra_args) - rehearsal = WhenRehearsal(spy_id=spy_id, spy_name=spy_name, payload=payload) + rehearsal = WhenRehearsal(spy=spy, payload=payload) self._log[-1] = rehearsal return rehearsal @@ -82,7 +82,7 @@ def consume_prop_rehearsal(self) -> PropRehearsal: except IndexError: raise MissingRehearsalError() - spy_id, spy_name, payload = event + spy, payload = event if ( not isinstance(event, SpyEvent) @@ -91,7 +91,7 @@ def consume_prop_rehearsal(self) -> PropRehearsal: ): raise MissingRehearsalError() - rehearsal = PropRehearsal(spy_id, spy_name, payload) + rehearsal = PropRehearsal(spy, payload) self._log[-1] = rehearsal return rehearsal @@ -100,7 +100,7 @@ def get_calls_to_verify(self, spy_ids: Sequence[int]) -> List[SpyEvent]: return [ event for event in self._log - if event.spy_id in spy_ids + if event.spy.id in spy_ids and isinstance(event, SpyEvent) and _is_verifiable(event) ] @@ -122,9 +122,9 @@ def _is_verifiable(event: AnySpyEvent) -> bool: def _apply_ignore_extra_args(event: AnySpyEvent, ignore_extra_args: bool) -> SpyEvent: - spy_id, spy_name, payload = event + spy, payload = event if isinstance(payload, SpyCall): payload = payload._replace(ignore_extra_args=ignore_extra_args) - return SpyEvent(spy_id=spy_id, spy_name=spy_name, payload=payload) + return SpyEvent(spy=spy, payload=payload) diff --git a/decoy/stringify.py b/decoy/stringify.py index 31202cc..554d94e 100644 --- a/decoy/stringify.py +++ b/decoy/stringify.py @@ -9,19 +9,17 @@ def stringify_call(event: AnySpyEvent) -> str: """Stringify the call to something human readable. ```python - SpyEvent( - spy_id=42, - spy_name="some_func", + SpyEvent(spy=SpyInfo(id=42, name="some_func"), payload=SpyCall(args=(1,), kwargs={"foo": False}) ) ``` ...would stringify as `"some_func(1, foo=False)"` """ - spy_id, spy_name, payload = event + spy, payload = event if not isinstance(payload, SpyCall): - full_prop_name = f"{spy_name}.{payload.prop_name}" + full_prop_name = f"{spy.name}.{payload.prop_name}" if payload.access_type == PropAccessType.SET: return f"{full_prop_name} = {payload.value}" @@ -36,7 +34,7 @@ def stringify_call(event: AnySpyEvent) -> str: extra_args_msg = ( " - ignoring unspecified arguments" if payload.ignore_extra_args else "" ) - return f"{spy_name}({', '.join(args_list + kwargs_list)}){extra_args_msg}" + return f"{spy.name}({', '.join(args_list + kwargs_list)}){extra_args_msg}" def stringify_call_list(calls: Sequence[AnySpyEvent]) -> str: diff --git a/decoy/warning_checker.py b/decoy/warning_checker.py index bba4d98..daa872c 100644 --- a/decoy/warning_checker.py +++ b/decoy/warning_checker.py @@ -29,7 +29,7 @@ def _check_no_miscalled_stubs(all_events: Sequence[AnySpyEvent]) -> None: for event in all_events: if isinstance(event.payload, SpyCall): - spy_id = event.spy_id + spy_id = event.spy.id spy_calls = all_calls_by_id.get(spy_id, []) all_calls_by_id[spy_id] = spy_calls + [event] diff --git a/tests/test_call_handler.py b/tests/test_call_handler.py index 9fbb833..5ce5c53 100644 --- a/tests/test_call_handler.py +++ b/tests/test_call_handler.py @@ -4,7 +4,7 @@ from decoy import Decoy from decoy.call_handler import CallHandler, CallHandlerResult from decoy.spy_log import SpyLog -from decoy.spy_events import SpyCall, SpyEvent, SpyPropAccess, PropAccessType +from decoy.spy_events import SpyInfo, SpyCall, SpyEvent, SpyPropAccess, PropAccessType from decoy.stub_store import StubBehavior, StubStore @@ -41,7 +41,7 @@ def test_handle_call_with_no_stubbing( ) -> None: """It should noop and add the call to the stack if no stubbing is configured.""" spy_call = SpyEvent( - spy_id=42, spy_name="spy_name", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="spy_name"), payload=SpyCall(args=(), kwargs={}) ) behavior = None @@ -61,7 +61,7 @@ def test_handle_call_with_return( ) -> None: """It return a Stub's configured return value.""" spy_call = SpyEvent( - spy_id=42, spy_name="spy_name", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="spy_name"), payload=SpyCall(args=(), kwargs={}) ) behavior = StubBehavior(return_value="hello world") @@ -81,7 +81,7 @@ def test_handle_call_with_raise( ) -> None: """It raise a Stub's configured error.""" spy_call = SpyEvent( - spy_id=42, spy_name="spy_name", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="spy_name"), payload=SpyCall(args=(), kwargs={}) ) behavior = StubBehavior(error=RuntimeError("oh no")) @@ -102,8 +102,7 @@ def test_handle_call_with_action( """It should trigger a stub's configured action.""" action = decoy.mock() spy_call = SpyEvent( - spy_id=42, - spy_name="spy_name", + spy=SpyInfo(id=42, name="spy_name"), payload=SpyCall(args=(1,), kwargs={"foo": "bar"}), ) behavior = StubBehavior(action=action) @@ -125,8 +124,7 @@ def test_handle_prop_get_with_action( """It should trigger a prop get stub's configured action.""" action = decoy.mock() spy_call = SpyEvent( - spy_id=42, - spy_name="spy_name", + spy=SpyInfo(id=42, name="spy_name"), payload=SpyPropAccess(prop_name="prop", access_type=PropAccessType.GET), ) behavior = StubBehavior(action=action) @@ -147,8 +145,7 @@ def test_handle_call_with_context_enter( ) -> None: """It should return a Stub's configured context value.""" spy_call = SpyEvent( - spy_id=42, - spy_name="spy_name", + spy=SpyInfo(id=42, name="spy_name"), payload=SpyCall(args=(), kwargs={}), ) behavior = StubBehavior(context_value="hello world") @@ -169,8 +166,7 @@ def test_handle_call_with_context_enter_none( ) -> None: """It should allow a configured context value to be None.""" spy_call = SpyEvent( - spy_id=42, - spy_name="spy_name", + spy=SpyInfo(id=42, name="spy_name"), payload=SpyCall(args=(), kwargs={}), ) behavior = StubBehavior(context_value=None) diff --git a/tests/test_core.py b/tests/test_core.py index 77b9729..33bae94 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5,8 +5,11 @@ from decoy.call_handler import CallHandler from decoy.spy_log import SpyLog from decoy.core import DecoyCore + +# from decoy.errors import MockNotAsyncError from decoy.spy import Spy, SpyCreator from decoy.spy_events import ( + SpyInfo, SpyCall, SpyPropAccess, SpyEvent, @@ -120,7 +123,7 @@ def test_mock_spec( call_handler: CallHandler, subject: DecoyCore, ) -> None: - """It should create a generic spy by default.""" + """It should create a spy from a source object.""" spy = decoy.mock(cls=Spy) decoy.when( @@ -140,7 +143,7 @@ def test_when_then_return( ) -> None: """It should be able to register a new stubbing.""" rehearsal = WhenRehearsal( - spy_id=1, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) decoy.when(spy_log.consume_when_rehearsal(ignore_extra_args=False)).then_return( rehearsal @@ -165,7 +168,7 @@ def test_when_then_return_multiple_values( ) -> None: """It should add multiple return values to a stub.""" rehearsal = WhenRehearsal( - spy_id=1, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) decoy.when(spy_log.consume_when_rehearsal(ignore_extra_args=False)).then_return( rehearsal @@ -198,7 +201,7 @@ def test_when_then_raise( ) -> None: """It should add a raise behavior to a stub.""" rehearsal = WhenRehearsal( - spy_id=1, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) decoy.when(spy_log.consume_when_rehearsal(ignore_extra_args=False)).then_return( rehearsal @@ -224,7 +227,8 @@ def test_when_then_do( ) -> None: """It should add an action behavior to a stub.""" rehearsal = WhenRehearsal( - spy_id=1, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="my_spy"), + payload=SpyCall(args=(), kwargs={}), ) decoy.when(spy_log.consume_when_rehearsal(ignore_extra_args=False)).then_return( rehearsal @@ -250,7 +254,7 @@ def test_when_then_enter_with( ) -> None: """It should be able to register a ContextManager stubbing.""" rehearsal = WhenRehearsal( - spy_id=1, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) decoy.when(spy_log.consume_when_rehearsal(ignore_extra_args=False)).then_return( rehearsal @@ -275,7 +279,7 @@ def test_when_ignore_extra_args( ) -> None: """It should be able to register a new stubbing.""" rehearsal = WhenRehearsal( - spy_id=1, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) decoy.when(spy_log.consume_when_rehearsal(ignore_extra_args=True)).then_return( rehearsal @@ -301,10 +305,10 @@ def test_verify( """It should be able to verify a call.""" spy_id = 42 rehearsal = VerifyRehearsal( - spy_id=spy_id, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=spy_id, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) call = SpyEvent( - spy_id=spy_id, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=spy_id, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) decoy.when( @@ -330,14 +334,16 @@ def test_verify_multiple_calls( rehearsals = [ VerifyRehearsal( - spy_id=spy_id_1, spy_name="spy_1", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=spy_id_1, name="spy_1"), payload=SpyCall(args=(), kwargs={}) ), VerifyRehearsal( - spy_id=spy_id_2, spy_name="spy_2", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=spy_id_2, name="spy_2"), payload=SpyCall(args=(), kwargs={}) ), ] calls = [ - SpyEvent(spy_id=spy_id_1, spy_name="spy_1", payload=SpyCall(args=(), kwargs={})) + SpyEvent( + spy=SpyInfo(id=spy_id_1, name="spy_1"), payload=SpyCall(args=(), kwargs={}) + ) ] decoy.when( @@ -364,10 +370,10 @@ def test_verify_call_times( """It should be able to verify the call count.""" spy_id = 42 rehearsal = VerifyRehearsal( - spy_id=spy_id, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=spy_id, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) call = SpyEvent( - spy_id=spy_id, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=spy_id, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) decoy.when( @@ -387,8 +393,7 @@ def test_prop( ) -> None: """It should be able to create set and delete rehearsals.""" rehearsal = PropRehearsal( - spy_id=1, - spy_name="my_spy", + spy=SpyInfo(id=1, name="my_spy"), payload=SpyPropAccess(prop_name="my_prop", access_type=PropAccessType.GET), ) @@ -399,8 +404,7 @@ def test_prop( result.set("hello") expected_set_event = SpyEvent( - spy_id=1, - spy_name="my_spy", + spy=SpyInfo(id=1, name="my_spy"), payload=SpyPropAccess( prop_name="my_prop", access_type=PropAccessType.SET, @@ -412,8 +416,7 @@ def test_prop( result.delete() expected_delete_event = SpyEvent( - spy_id=1, - spy_name="my_spy", + spy=SpyInfo(id=1, name="my_spy"), payload=SpyPropAccess(prop_name="my_prop", access_type=PropAccessType.DELETE), ) decoy.verify(spy_log.push(expected_delete_event), times=1) @@ -427,7 +430,9 @@ def test_reset( subject: DecoyCore, ) -> None: """It should reset the stores.""" - call = SpyEvent(spy_id=1, spy_name="my_spy", payload=SpyCall(args=(), kwargs={})) + call = SpyEvent( + spy=SpyInfo(id=1, name="my_spy"), payload=SpyCall(args=(), kwargs={}) + ) decoy.when(spy_log.get_all()).then_return([call]) diff --git a/tests/test_errors.py b/tests/test_errors.py index 502ff30..d1b4ecd 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -3,7 +3,7 @@ import os from typing import List, NamedTuple, Optional -from decoy.spy_events import SpyCall, SpyEvent, VerifyRehearsal +from decoy.spy_events import SpyCall, SpyEvent, SpyInfo, VerifyRehearsal from decoy.errors import VerifyError @@ -20,7 +20,7 @@ class VerifyErrorSpec(NamedTuple): VerifyErrorSpec( rehearsals=[ VerifyRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ), ], calls=[], @@ -36,18 +36,16 @@ class VerifyErrorSpec(NamedTuple): VerifyErrorSpec( rehearsals=[ VerifyRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), ], @@ -65,35 +63,29 @@ class VerifyErrorSpec(NamedTuple): VerifyErrorSpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), VerifyRehearsal( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=(7, 8, 9), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), SpyEvent( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=("oh no",), kwargs={}), ), ], @@ -114,20 +106,17 @@ class VerifyErrorSpec(NamedTuple): VerifyErrorSpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], @@ -143,15 +132,13 @@ class VerifyErrorSpec(NamedTuple): VerifyErrorSpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), ], diff --git a/tests/test_spy.py b/tests/test_spy.py index 336a0f4..38baec5 100644 --- a/tests/test_spy.py +++ b/tests/test_spy.py @@ -5,9 +5,9 @@ from decoy import Decoy from decoy.call_handler import CallHandler, CallHandlerResult -from decoy.spec import Spec, BoundArgs -from decoy.spy import SpyCreator, Spy, AsyncSpy -from decoy.spy_events import SpyCall, SpyEvent, SpyPropAccess, PropAccessType +from decoy.spy import AsyncSpy, Spy, SpyCreator +from decoy.spy_core import BoundArgs, SpyCore +from decoy.spy_events import SpyCall, SpyEvent, SpyInfo, SpyPropAccess, PropAccessType from .common import SomeClass, some_func @@ -15,50 +15,51 @@ pytestmark = pytest.mark.asyncio -@pytest.fixture() +@pytest.fixture def call_handler(decoy: Decoy) -> CallHandler: """Get a mock CallHandler.""" return decoy.mock(cls=CallHandler) -@pytest.fixture() +@pytest.fixture def spy_creator(decoy: Decoy) -> SpyCreator: """Get a mock SpyCreator.""" return decoy.mock(cls=SpyCreator) -@pytest.fixture() -def spec(decoy: Decoy) -> Spec: - """Get a mock Spec.""" - return decoy.mock(cls=Spec) +@pytest.fixture +def spy_core(decoy: Decoy) -> SpyCore: + """Get a mock SpyCore.""" + return decoy.mock(cls=SpyCore) def test_create_spy(decoy: Decoy, call_handler: CallHandler) -> None: """It should get default configurations from the spec.""" - spec = decoy.mock(cls=Spec) - sig = inspect.signature(some_func) + core = decoy.mock(cls=SpyCore) + signature = inspect.signature(some_func) - decoy.when(spec.get_full_name()).then_return("hello.world") - decoy.when(spec.get_signature()).then_return(sig) - decoy.when(spec.get_class_type()).then_return(SomeClass) + decoy.when(core.full_name).then_return("hello.world") + decoy.when(core.signature).then_return(signature) + decoy.when(core.class_type).then_return(SomeClass) + decoy.when(core.is_async).then_return(False) subject = SpyCreator(call_handler=call_handler) - result = subject.create(spec=spec, name="foo") + result = subject.create(core=core) assert isinstance(result, Spy) assert isinstance(result, SomeClass) - assert inspect.signature(result) == sig + assert inspect.signature(result) == signature assert repr(result) == "" def test_create_async_spy(decoy: Decoy, call_handler: CallHandler) -> None: """It should get default configurations from the spec.""" - spec = decoy.mock(cls=Spec) + core = decoy.mock(cls=SpyCore) - decoy.when(spec.get_is_async()).then_return(True) + decoy.when(core.is_async).then_return(True) subject = SpyCreator(call_handler=call_handler) - result = subject.create(spec=spec) + result = subject.create(core=core) assert isinstance(result, AsyncSpy) @@ -69,17 +70,17 @@ def test_child_spy( spy_creator: SpyCreator, ) -> None: """It should create a child spy.""" - parent_spec = decoy.mock(cls=Spec) - child_spec = decoy.mock(cls=Spec) + parent_core = decoy.mock(cls=SpyCore) + child_core = decoy.mock(cls=SpyCore) child_spy = decoy.mock(cls=Spy) - decoy.when(parent_spec.get_child_spec("child")).then_return(child_spec) - decoy.when(spy_creator.create(spec=child_spec, is_async=False)).then_return( - child_spy + decoy.when(parent_core.create_child_core("child", is_async=False)).then_return( + child_core ) + decoy.when(spy_creator.create(core=child_core)).then_return(child_spy) subject = Spy( - spec=parent_spec, + core=parent_core, call_handler=call_handler, spy_creator=spy_creator, ) @@ -94,19 +95,21 @@ def test_child_spy_caching( spy_creator: SpyCreator, ) -> None: """It should create a child spy only once.""" - parent_spec = decoy.mock(cls=Spec) - child_spec = decoy.mock(cls=Spec) + parent_core = decoy.mock(cls=SpyCore) + child_core = decoy.mock(cls=SpyCore) child_spy = decoy.mock(cls=Spy) wrong_spy = decoy.mock(cls=Spy) - decoy.when(parent_spec.get_child_spec("child")).then_return(child_spec) - decoy.when(spy_creator.create(spec=child_spec, is_async=False)).then_return( + decoy.when(parent_core.create_child_core("child", is_async=False)).then_return( + child_core + ) + decoy.when(spy_creator.create(core=child_core)).then_return( child_spy, wrong_spy, ) subject = Spy( - spec=parent_spec, + core=parent_core, call_handler=call_handler, spy_creator=spy_creator, ) @@ -119,25 +122,25 @@ def test_spy_calls( decoy: Decoy, call_handler: CallHandler, spy_creator: SpyCreator, - spec: Spec, + spy_core: SpyCore, ) -> None: """It should send any calls to the call handler.""" - subject = Spy(spec=spec, call_handler=call_handler, spy_creator=spy_creator) + spy_info = SpyInfo(id=123456, name="spy_name") - decoy.when(spec.get_name()).then_return("spy_name") - decoy.when(spec.bind_args(1, 2, three=3)).then_return( + decoy.when(spy_core.info).then_return(spy_info) + decoy.when(spy_core.bind_args(1, 2, three=3)).then_return( BoundArgs(args=(1, 2, 3), kwargs={}) ) decoy.when( call_handler.handle( SpyEvent( - spy_id=id(subject), - spy_name="spy_name", + spy=spy_info, payload=SpyCall(args=(1, 2, 3), kwargs={}), ) ) ).then_return(CallHandlerResult(42)) + subject = Spy(core=spy_core, call_handler=call_handler, spy_creator=spy_creator) result = subject(1, 2, three=3) assert result == 42 @@ -147,25 +150,26 @@ def test_spy_context_manager( decoy: Decoy, call_handler: CallHandler, spy_creator: SpyCreator, - spec: Spec, + spy_core: SpyCore, ) -> None: """It should be usable in a `with` statement.""" - enter_spec = decoy.mock(cls=Spec) - exit_spec = decoy.mock(cls=Spec) + enter_core = decoy.mock(cls=SpyCore) + exit_core = decoy.mock(cls=SpyCore) enter_spy = decoy.mock(cls=Spy) exit_spy = decoy.mock(cls=Spy) error = RuntimeError("oh no") - decoy.when(spec.get_name()).then_return("spy_name") - decoy.when(spec.get_child_spec("__enter__")).then_return(enter_spec) - decoy.when(spec.get_child_spec("__exit__")).then_return(exit_spec) - decoy.when(spy_creator.create(spec=enter_spec, is_async=False)).then_return( - enter_spy + decoy.when(spy_core.create_child_core("__enter__", is_async=False)).then_return( + enter_core + ) + decoy.when(spy_core.create_child_core("__exit__", is_async=False)).then_return( + exit_core ) - decoy.when(spy_creator.create(spec=exit_spec, is_async=False)).then_return(exit_spy) + decoy.when(spy_creator.create(core=enter_core)).then_return(enter_spy) + decoy.when(spy_creator.create(core=exit_core)).then_return(exit_spy) decoy.when(enter_spy()).then_return(42) - subject = Spy(spec=spec, call_handler=call_handler, spy_creator=spy_creator) + subject = Spy(core=spy_core, call_handler=call_handler, spy_creator=spy_creator) tb = None try: @@ -183,25 +187,26 @@ async def test_spy_async_context_manager( decoy: Decoy, call_handler: CallHandler, spy_creator: SpyCreator, - spec: Spec, + spy_core: SpyCore, ) -> None: """It should be usable in an `async with` statement.""" - enter_spec = decoy.mock(cls=Spec) - exit_spec = decoy.mock(cls=Spec) + enter_core = decoy.mock(cls=SpyCore) + exit_core = decoy.mock(cls=SpyCore) enter_spy = decoy.mock(cls=AsyncSpy) exit_spy = decoy.mock(cls=AsyncSpy) error = RuntimeError("oh no") - decoy.when(spec.get_name()).then_return("spy_name") - decoy.when(spec.get_child_spec("__aenter__")).then_return(enter_spec) - decoy.when(spec.get_child_spec("__aexit__")).then_return(exit_spec) - decoy.when(spy_creator.create(spec=enter_spec, is_async=True)).then_return( - enter_spy + decoy.when(spy_core.create_child_core("__aenter__", is_async=True)).then_return( + enter_core + ) + decoy.when(spy_core.create_child_core("__aexit__", is_async=True)).then_return( + exit_core ) - decoy.when(spy_creator.create(spec=exit_spec, is_async=True)).then_return(exit_spy) + decoy.when(spy_creator.create(core=enter_core)).then_return(enter_spy) + decoy.when(spy_creator.create(core=exit_core)).then_return(exit_spy) decoy.when(await enter_spy()).then_return(42) - subject = Spy(spec=spec, call_handler=call_handler, spy_creator=spy_creator) + subject = Spy(core=spy_core, call_handler=call_handler, spy_creator=spy_creator) tb = None try: @@ -219,17 +224,16 @@ def test_spy_prop_get( decoy: Decoy, call_handler: CallHandler, spy_creator: SpyCreator, - spec: Spec, + spy_core: SpyCore, ) -> None: """It should record a property get call.""" - subject = Spy(spec=spec, call_handler=call_handler, spy_creator=spy_creator) + spy_info = SpyInfo(id=123456, name="spy_name") - decoy.when(spec.get_name()).then_return("spy_name") + decoy.when(spy_core.info).then_return(spy_info) decoy.when( call_handler.handle( SpyEvent( - spy_id=id(subject), - spy_name="spy_name", + spy=spy_info, payload=SpyPropAccess( prop_name="some_property", access_type=PropAccessType.GET, @@ -238,6 +242,7 @@ def test_spy_prop_get( ) ).then_return(CallHandlerResult(42)) + subject = Spy(core=spy_core, call_handler=call_handler, spy_creator=spy_creator) result = subject.some_property assert result == 42 @@ -247,20 +252,21 @@ def test_spy_prop_set( decoy: Decoy, call_handler: CallHandler, spy_creator: SpyCreator, - spec: Spec, + spy_core: SpyCore, ) -> None: """It should record a property set call.""" - decoy.when(spec.get_name()).then_return("spy_name") + spy_info = SpyInfo(id=123456, name="spy_name") - subject = Spy(spec=spec, call_handler=call_handler, spy_creator=spy_creator) + decoy.when(spy_core.info).then_return(spy_info) + + subject = Spy(core=spy_core, call_handler=call_handler, spy_creator=spy_creator) subject.some_property = 42 assert subject.some_property == 42 decoy.verify( call_handler.handle( SpyEvent( - spy_id=id(subject), - spy_name="spy_name", + spy=spy_info, payload=SpyPropAccess( prop_name="some_property", access_type=PropAccessType.SET, @@ -275,12 +281,14 @@ def test_spy_prop_delete( decoy: Decoy, call_handler: CallHandler, spy_creator: SpyCreator, - spec: Spec, + spy_core: SpyCore, ) -> None: """It should record a property set call.""" - decoy.when(spec.get_name()).then_return("spy_name") + spy_info = SpyInfo(id=123456, name="spy_name") + + decoy.when(spy_core.info).then_return(spy_info) - subject = Spy(spec=spec, call_handler=call_handler, spy_creator=spy_creator) + subject = Spy(core=spy_core, call_handler=call_handler, spy_creator=spy_creator) subject.some_property = 42 del subject.some_property @@ -289,8 +297,7 @@ def test_spy_prop_delete( decoy.verify( call_handler.handle( SpyEvent( - spy_id=id(subject), - spy_name="spy_name", + spy=spy_info, payload=SpyPropAccess( prop_name="some_property", access_type=PropAccessType.DELETE, diff --git a/tests/test_spec.py b/tests/test_spy_core.py similarity index 67% rename from tests/test_spec.py rename to tests/test_spy_core.py index cb770c0..eedb955 100644 --- a/tests/test_spec.py +++ b/tests/test_spy_core.py @@ -1,9 +1,9 @@ -"""Tests for Spec instances.""" +"""Tests for SpyCore instances.""" import pytest import inspect from typing import Any, Dict, NamedTuple, Optional, Tuple, Type -from decoy.spec import Spec, BoundArgs +from decoy.spy_core import SpyCore, BoundArgs from decoy.warnings import IncorrectCallWarning from .common import ( SomeClass, @@ -19,17 +19,20 @@ def test_init() -> None: """It should have default spec properties.""" - subject = Spec(source=None, name=None) + subject = SpyCore(source=None, name=None) - assert subject.get_signature() is None - assert subject.get_class_type() is None - assert subject.get_is_async() is False + assert isinstance(subject.info.id, int) + assert subject.signature is None + assert subject.class_type is None + assert subject.is_async is False + + assert subject.info.id != SpyCore(source=None, name=None).info.id class GetNameSpec(NamedTuple): - """Spec data to test get_name and get_full_name.""" + """Spec data to test info and full_name.""" - subject: Spec + subject: SpyCore expected_name: str expected_full_name: str @@ -38,51 +41,57 @@ class GetNameSpec(NamedTuple): GetNameSpec._fields, [ GetNameSpec( - subject=Spec(source=None, name=None), + subject=SpyCore(source=None, name=None), expected_name="unnamed", expected_full_name="unnamed", ), GetNameSpec( - subject=Spec(source=some_func, name=None), + subject=SpyCore(source=some_func, name=None), expected_name="some_func", expected_full_name="tests.common.some_func", ), GetNameSpec( - subject=Spec(source=some_func, name="spy_name", module_name="module_name"), + subject=SpyCore( + source=some_func, name="spy_name", module_name="module_name" + ), expected_name="spy_name", expected_full_name="module_name.spy_name", ), GetNameSpec( - subject=Spec(source=some_func, name="spy_name", module_name=None), + subject=SpyCore(source=some_func, name="spy_name", module_name=None), expected_name="spy_name", expected_full_name="spy_name", ), GetNameSpec( - subject=Spec(source=SomeClass, name=None).get_child_spec("foo"), + subject=SpyCore(source=SomeClass, name=None).create_child_core( + "foo", is_async=False + ), expected_name="SomeClass.foo", expected_full_name="tests.common.SomeClass.foo", ), GetNameSpec( subject=( - Spec(source=SomeNestedClass, name=None) - .get_child_spec("child") - .get_child_spec("foo") + SpyCore(source=SomeNestedClass, name=None) + .create_child_core("child", is_async=False) + .create_child_core("foo", is_async=False) ), expected_name="SomeNestedClass.child.foo", expected_full_name="tests.common.SomeNestedClass.child.foo", ), ], ) -def test_get_name(subject: Spec, expected_name: str, expected_full_name: str) -> None: +def test_get_name( + subject: SpyCore, expected_name: str, expected_full_name: str +) -> None: """It should assign names from args or spec.""" - assert subject.get_name() == expected_name - assert subject.get_full_name() == expected_full_name + assert subject.info.name == expected_name + assert subject.full_name == expected_full_name class GetSignatureSpec(NamedTuple): """Spec data to test get_signature.""" - subject: Spec + subject: SpyCore expected_signature: Optional[inspect.Signature] @@ -90,11 +99,11 @@ class GetSignatureSpec(NamedTuple): GetSignatureSpec._fields, [ GetSignatureSpec( - subject=Spec(source=None, name=None), + subject=SpyCore(source=None, name=None), expected_signature=None, ), GetSignatureSpec( - subject=Spec(source=some_func, name=None), + subject=SpyCore(source=some_func, name=None), expected_signature=inspect.Signature( parameters=[ inspect.Parameter( @@ -107,7 +116,9 @@ class GetSignatureSpec(NamedTuple): ), ), GetSignatureSpec( - subject=Spec(source=SomeClass, name=None).get_child_spec("foo"), + subject=SpyCore(source=SomeClass, name=None).create_child_core( + "foo", is_async=False + ), expected_signature=inspect.Signature( parameters=[ inspect.Parameter( @@ -120,16 +131,16 @@ class GetSignatureSpec(NamedTuple): ), ), GetSignatureSpec( - subject=Spec(source=SomeClass, name=None).get_child_spec( - "primitive_property" + subject=SpyCore(source=SomeClass, name=None).create_child_core( + "primitive_property", is_async=False ), expected_signature=None, ), GetSignatureSpec( subject=( - Spec(source=SomeNestedClass, name=None) - .get_child_spec("child") - .get_child_spec("foo") + SpyCore(source=SomeNestedClass, name=None) + .create_child_core("child", is_async=False) + .create_child_core("foo", is_async=False) ), expected_signature=inspect.Signature( parameters=[ @@ -144,9 +155,9 @@ class GetSignatureSpec(NamedTuple): ), GetSignatureSpec( subject=( - Spec(source=SomeNestedClass, name=None) - .get_child_spec("child_attr") - .get_child_spec("foo") + SpyCore(source=SomeNestedClass, name=None) + .create_child_core("child_attr", is_async=False) + .create_child_core("foo", is_async=False) ), expected_signature=inspect.Signature( parameters=[ @@ -160,7 +171,9 @@ class GetSignatureSpec(NamedTuple): ), ), GetSignatureSpec( - subject=Spec(source=SomeClass, name=None).get_child_spec("fizzbuzz"), + subject=SpyCore(source=SomeClass, name=None).create_child_core( + "fizzbuzz", is_async=False + ), expected_signature=inspect.Signature( parameters=[ inspect.Parameter( @@ -173,7 +186,7 @@ class GetSignatureSpec(NamedTuple): ), ), GetSignatureSpec( - subject=Spec(source=SomeCallableClass, name=None), + subject=SpyCore(source=SomeCallableClass, name=None), expected_signature=inspect.Signature( parameters=[ inspect.Parameter( @@ -186,7 +199,7 @@ class GetSignatureSpec(NamedTuple): ), ), GetSignatureSpec( - subject=Spec(source=some_wrapped_func, name=None), + subject=SpyCore(source=some_wrapped_func, name=None), expected_signature=inspect.Signature( parameters=[ inspect.Parameter( @@ -199,8 +212,8 @@ class GetSignatureSpec(NamedTuple): ), ), GetSignatureSpec( - subject=Spec(source=SomeClass, name=None).get_child_spec( - "some_wrapped_method" + subject=SpyCore(source=SomeClass, name=None).create_child_core( + "some_wrapped_method", is_async=False ), expected_signature=inspect.Signature( parameters=[ @@ -216,11 +229,11 @@ class GetSignatureSpec(NamedTuple): ], ) def test_get_signature( - subject: Spec, + subject: SpyCore, expected_signature: Optional[inspect.Signature], ) -> None: """It should inspect the spec source's signature.""" - assert subject.get_signature() == expected_signature + assert subject.signature == expected_signature @pytest.mark.filterwarnings("ignore:'NoneType' object is not subscriptable") @@ -233,9 +246,11 @@ class _BadTypeHints: def _ok(self, hello: str) -> None: ... - subject = Spec(source=_BadTypeHints, name=None).get_child_spec("_ok") + subject = SpyCore(source=_BadTypeHints, name=None).create_child_core( + "_ok", is_async=False + ) - assert subject.get_signature() == inspect.Signature( + assert subject.signature == inspect.Signature( parameters=[ inspect.Parameter( name="hello", @@ -250,7 +265,7 @@ def _ok(self, hello: str) -> None: class GetClassTypeSpec(NamedTuple): """Spec data to test get_class_type.""" - subject: Spec + subject: SpyCore expected_class_type: Optional[Type[Any]] @@ -258,28 +273,28 @@ class GetClassTypeSpec(NamedTuple): GetClassTypeSpec._fields, [ GetClassTypeSpec( - subject=Spec(source=None, name=None), + subject=SpyCore(source=None, name=None), expected_class_type=None, ), GetClassTypeSpec( - subject=Spec(source=some_func, name=None), + subject=SpyCore(source=some_func, name=None), expected_class_type=None, ), GetClassTypeSpec( - subject=Spec(source=SomeClass, name=None), + subject=SpyCore(source=SomeClass, name=None), expected_class_type=SomeClass, ), ], ) -def test_get_class_type(subject: Spec, expected_class_type: Type[Any]) -> None: +def test_get_class_type(subject: SpyCore, expected_class_type: Type[Any]) -> None: """It should get the class type, if source is a class.""" - assert subject.get_class_type() == expected_class_type + assert subject.class_type == expected_class_type class GetIsAsyncSpec(NamedTuple): """Spec data to test get_is_async.""" - subject: Spec + subject: SpyCore expected_is_async: bool @@ -287,40 +302,52 @@ class GetIsAsyncSpec(NamedTuple): GetIsAsyncSpec._fields, [ GetIsAsyncSpec( - subject=Spec(source=None, name=None), + subject=SpyCore(source=None, name=None), expected_is_async=False, ), GetIsAsyncSpec( - subject=Spec(source=some_func, name=None), + subject=SpyCore(source=None, name=None, is_async=True), + expected_is_async=True, + ), + GetIsAsyncSpec( + subject=SpyCore(source=some_func, name=None), expected_is_async=False, ), GetIsAsyncSpec( - subject=Spec(source=SomeClass, name=None), + subject=SpyCore(source=SomeClass, name=None), expected_is_async=False, ), GetIsAsyncSpec( - subject=Spec(source=some_async_func, name=None), + subject=SpyCore(source=some_async_func, name=None), expected_is_async=True, ), GetIsAsyncSpec( - subject=Spec(source=SomeAsyncCallableClass, name=None), + subject=SpyCore(source=SomeAsyncCallableClass, name=None), expected_is_async=True, ), GetIsAsyncSpec( - subject=Spec(source=SomeAsyncClass, name=None).get_child_spec("foo"), + subject=SpyCore(source=SomeAsyncClass, name=None).create_child_core( + "foo", is_async=False + ), + expected_is_async=True, + ), + GetIsAsyncSpec( + subject=SpyCore(source=None, name=None).create_child_core( + "foo", is_async=True + ), expected_is_async=True, ), ], ) -def test_get_is_async(subject: Spec, expected_is_async: bool) -> None: +def test_get_is_async(subject: SpyCore, expected_is_async: bool) -> None: """It should get whether the Spec represents an async callable.""" - assert subject.get_is_async() is expected_is_async + assert subject.is_async is expected_is_async class GetBindArgsSpec(NamedTuple): """Spec data to test bind_args.""" - subject: Spec + subject: SpyCore input_args: Tuple[Any, ...] input_kwargs: Dict[str, Any] expected_args: Tuple[Any, ...] @@ -331,21 +358,21 @@ class GetBindArgsSpec(NamedTuple): GetBindArgsSpec._fields, [ GetBindArgsSpec( - subject=Spec(source=None, name=None), + subject=SpyCore(source=None, name=None), input_args=(1, 2, 3), input_kwargs={"four": "five", "six": "seven"}, expected_args=(1, 2, 3), expected_kwargs={"four": "five", "six": "seven"}, ), GetBindArgsSpec( - subject=Spec(source=some_func, name=None), + subject=SpyCore(source=some_func, name=None), input_args=(), input_kwargs={"val": "hello"}, expected_args=("hello",), expected_kwargs={}, ), GetBindArgsSpec( - subject=Spec(source=some_wrapped_func, name=None), + subject=SpyCore(source=some_wrapped_func, name=None), input_args=(), input_kwargs={"val": "hello"}, expected_args=("hello",), @@ -354,7 +381,7 @@ class GetBindArgsSpec(NamedTuple): ], ) def test_bind_args( - subject: Spec, + subject: SpyCore, input_args: Tuple[Any, ...], input_kwargs: Dict[str, Any], expected_args: Tuple[Any, ...], @@ -368,7 +395,7 @@ def test_bind_args( def test_warn_if_called_incorrectly() -> None: """It should trigger a warning if bound_args is called incorrectly.""" - subject = Spec(source=some_func, name=None) + subject = SpyCore(source=some_func, name=None) with pytest.warns(IncorrectCallWarning, match="missing a required argument"): subject.bind_args(wrong_arg_name="1") diff --git a/tests/test_spy_events.py b/tests/test_spy_events.py index 69f8ca5..c7b2ebe 100644 --- a/tests/test_spy_events.py +++ b/tests/test_spy_events.py @@ -6,6 +6,7 @@ PropAccessType, SpyCall, SpyEvent, + SpyInfo, SpyPropAccess, SpyRehearsal, WhenRehearsal, @@ -25,117 +26,99 @@ class MatchEventSpec(NamedTuple): match_event_specs: List[MatchEventSpec] = [ MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ), rehearsal=WhenRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ), expected_result=True, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2), kwargs={"foo": "bar", "baz": "qux"}), ), rehearsal=VerifyRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2), kwargs={"foo": "bar", "baz": "qux"}), ), expected_result=True, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2), kwargs={"foo": "bar", "baz": "qux"}), ), rehearsal=WhenRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1,), kwargs={"baz": "qux"}, ignore_extra_args=True), ), expected_result=True, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ), rehearsal=VerifyRehearsal( - spy_id=21, - spy_name="my_spy", + spy=SpyInfo(id=21, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ), expected_result=False, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1,), kwargs={}), ), rehearsal=WhenRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ), expected_result=False, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ), rehearsal=VerifyRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1,), kwargs={}), ), expected_result=False, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2), kwargs={"foo": "bar", "baz": "qux"}), ), rehearsal=WhenRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(2,), kwargs={"baz": "qux"}, ignore_extra_args=True), ), expected_result=False, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2), kwargs={"foo": "bar", "baz": "qux"}), ), rehearsal=VerifyRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2), kwargs={"foo": "qux"}, ignore_extra_args=True), ), expected_result=False, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2), kwargs={"foo": "bar", "baz": "qux"}), ), rehearsal=WhenRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall( args=(1, 2, 3), kwargs={"foo": "bar", "baz": "qux"}, @@ -146,13 +129,11 @@ class MatchEventSpec(NamedTuple): ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2), kwargs={"foo": "bar", "baz": "qux"}), ), rehearsal=VerifyRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall( args=(1, 2), kwargs={"foo": "bar", "baz": "qux", "fizz": "buzz"}, @@ -163,26 +144,22 @@ class MatchEventSpec(NamedTuple): ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyPropAccess(prop_name="prop", access_type=PropAccessType.GET), ), rehearsal=WhenRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyPropAccess(prop_name="prop", access_type=PropAccessType.GET), ), expected_result=True, ), MatchEventSpec( event=SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyPropAccess(prop_name="prop", access_type=PropAccessType.DELETE), ), rehearsal=WhenRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyPropAccess(prop_name="prop", access_type=PropAccessType.GET), ), expected_result=False, diff --git a/tests/test_spy_log.py b/tests/test_spy_log.py index bb4f2e1..97fb080 100644 --- a/tests/test_spy_log.py +++ b/tests/test_spy_log.py @@ -5,6 +5,7 @@ from decoy.spy_events import ( SpyCall, SpyEvent, + SpyInfo, SpyPropAccess, PropAccessType, WhenRehearsal, @@ -18,8 +19,7 @@ def test_push_and_consume_when_rehearsal() -> None: """It should be able to push and pop from the stack.""" subject = SpyLog() call = SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ) @@ -34,8 +34,7 @@ def test_push_and_consume_when_rehearsal_ignore_extra_args() -> None: """It should be able to push and pop from the stack while ignoring extra args.""" subject = SpyLog() call = SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ) @@ -51,8 +50,7 @@ def test_push_and_consume_prop_rehearsal_for_when() -> None: """It should be able to push and consume a prop rehearsal for stubbing.""" subject = SpyLog() event = SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyPropAccess(prop_name="my_prop", access_type=PropAccessType.GET), ) @@ -70,8 +68,7 @@ def test_consume_when_rehearsal_raises_empty_error() -> None: subject.consume_when_rehearsal(ignore_extra_args=False) call = SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ) subject.push(call) @@ -85,8 +82,7 @@ def test_push_and_consume_prop_rehearsal_for_prop() -> None: """It should be able to push and consume a prop rehearsal for more rehearsals.""" subject = SpyLog() event = SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyPropAccess(prop_name="my_prop", access_type=PropAccessType.GET), ) @@ -104,8 +100,7 @@ def test_consume_prop_rehearsal_raises_empty_error() -> None: subject.consume_prop_rehearsal() event = SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyPropAccess(prop_name="my_prop", access_type=PropAccessType.GET), ) subject.push(event) @@ -115,8 +110,7 @@ def test_consume_prop_rehearsal_raises_empty_error() -> None: subject.consume_prop_rehearsal() call = SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}), ) subject.push(call) @@ -125,8 +119,7 @@ def test_consume_prop_rehearsal_raises_empty_error() -> None: subject.consume_prop_rehearsal() event = SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyPropAccess(prop_name="my_prop", access_type=PropAccessType.DELETE), ) subject.push(event) @@ -138,8 +131,12 @@ def test_consume_prop_rehearsal_raises_empty_error() -> None: def test_consume_verify_rehearsals() -> None: """It should be able to pop a slice off the stack, retaining order.""" subject = SpyLog() - call_1 = SpyEvent(spy_id=1, spy_name="spy_1", payload=SpyCall(args=(), kwargs={})) - call_2 = SpyEvent(spy_id=2, spy_name="spy_2", payload=SpyCall(args=(), kwargs={})) + call_1 = SpyEvent( + spy=SpyInfo(id=1, name="spy_1"), payload=SpyCall(args=(), kwargs={}) + ) + call_2 = SpyEvent( + spy=SpyInfo(id=2, name="spy_2"), payload=SpyCall(args=(), kwargs={}) + ) subject.push(call_1) subject.push(call_2) @@ -147,13 +144,11 @@ def test_consume_verify_rehearsals() -> None: result = subject.consume_verify_rehearsals(count=2, ignore_extra_args=False) assert result == [ VerifyRehearsal( - spy_id=1, - spy_name="spy_1", + spy=SpyInfo(id=1, name="spy_1"), payload=SpyCall(args=(), kwargs={}), ), VerifyRehearsal( - spy_id=2, - spy_name="spy_2", + spy=SpyInfo(id=2, name="spy_2"), payload=SpyCall(args=(), kwargs={}), ), ] @@ -168,8 +163,12 @@ def test_consume_verify_rehearsals() -> None: def test_consume_verify_rehearsals_ignore_extra_args() -> None: """It should be able to pop a slice off the stack, retaining order.""" subject = SpyLog() - call_1 = SpyEvent(spy_id=1, spy_name="spy_1", payload=SpyCall(args=(), kwargs={})) - call_2 = SpyEvent(spy_id=2, spy_name="spy_2", payload=SpyCall(args=(), kwargs={})) + call_1 = SpyEvent( + spy=SpyInfo(id=1, name="spy_1"), payload=SpyCall(args=(), kwargs={}) + ) + call_2 = SpyEvent( + spy=SpyInfo(id=2, name="spy_2"), payload=SpyCall(args=(), kwargs={}) + ) subject.push(call_1) subject.push(call_2) @@ -177,13 +176,11 @@ def test_consume_verify_rehearsals_ignore_extra_args() -> None: result = subject.consume_verify_rehearsals(count=2, ignore_extra_args=True) assert result == [ VerifyRehearsal( - spy_id=1, - spy_name="spy_1", + spy=SpyInfo(id=1, name="spy_1"), payload=SpyCall(args=(), kwargs={}, ignore_extra_args=True), ), VerifyRehearsal( - spy_id=2, - spy_name="spy_2", + spy=SpyInfo(id=2, name="spy_2"), payload=SpyCall(args=(), kwargs={}, ignore_extra_args=True), ), ] @@ -193,21 +190,18 @@ def test_consume_verify_rehearsals_ignores_prop_gets() -> None: """It should be able to pop a slice off the stack, retaining order.""" subject = SpyLog() call_1 = SpyEvent( - spy_id=101, spy_name="spy_1", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(1,), kwargs={}) ) call_2 = SpyEvent( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyPropAccess(prop_name="child", access_type=PropAccessType.GET), ) call_3 = SpyEvent( - spy_id=102, - spy_name="spy_1.child", + spy=SpyInfo(id=102, name="spy_1.child"), payload=SpyCall(args=(2,), kwargs={}), ) call_4 = SpyEvent( - spy_id=102, - spy_name="spy_1.child", + spy=SpyInfo(id=102, name="spy_1.child"), payload=SpyPropAccess(prop_name="fizz", access_type=PropAccessType.DELETE), ) subject.push(call_1) @@ -218,18 +212,15 @@ def test_consume_verify_rehearsals_ignores_prop_gets() -> None: result = subject.consume_verify_rehearsals(count=3, ignore_extra_args=False) assert result == [ VerifyRehearsal( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(1,), kwargs={}), ), VerifyRehearsal( - spy_id=102, - spy_name="spy_1.child", + spy=SpyInfo(id=102, name="spy_1.child"), payload=SpyCall(args=(2,), kwargs={}), ), VerifyRehearsal( - spy_id=102, - spy_name="spy_1.child", + spy=SpyInfo(id=102, name="spy_1.child"), payload=SpyPropAccess(prop_name="fizz", access_type=PropAccessType.DELETE), ), ] @@ -238,7 +229,9 @@ def test_consume_verify_rehearsals_ignores_prop_gets() -> None: def test_consume_verify_rehearsals_raises_error() -> None: """It should raise an error if the stack has too few members to pop a slice.""" subject = SpyLog() - call_1 = SpyEvent(spy_id=1, spy_name="spy_1", payload=SpyCall(args=(), kwargs={})) + call_1 = SpyEvent( + spy=SpyInfo(id=1, name="spy_1"), payload=SpyCall(args=(), kwargs={}) + ) subject.push(call_1) @@ -250,23 +243,19 @@ def test_get_calls_to_verify() -> None: """It can get a list of calls made matching spy IDs of given rehearsals.""" subject = SpyLog() call_1 = SpyEvent( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(1,), kwargs={}), ) call_2 = SpyEvent( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(2,), kwargs={}), ) call_3 = SpyEvent( - spy_id=202, - spy_name="spy_2", + spy=SpyInfo(id=202, name="spy_2"), payload=SpyCall(args=(1,), kwargs={}), ) call_4 = SpyEvent( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(1,), kwargs={}), ) @@ -291,23 +280,19 @@ def test_get_calls_to_verify_skips_prop_gets() -> None: subject = SpyLog() call_1 = SpyEvent( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyPropAccess(prop_name="child", access_type=PropAccessType.GET), ) call_2 = PropRehearsal( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyPropAccess(prop_name="child", access_type=PropAccessType.GET), ) call_3 = SpyEvent( - spy_id=102, - spy_name="spy_1.child", + spy=SpyInfo(id=102, name="spy_1.child"), payload=SpyCall(args=(2,), kwargs={}), ) call_4 = SpyEvent( - spy_id=102, - spy_name="spy_1.child", + spy=SpyInfo(id=102, name="spy_1.child"), payload=SpyPropAccess(prop_name="fizz", access_type=PropAccessType.DELETE), ) @@ -322,9 +307,15 @@ def test_get_calls_to_verify_skips_prop_gets() -> None: def test_get_all() -> None: """It can get a list of all calls and rehearsals.""" subject = SpyLog() - call_1 = SpyEvent(spy_id=101, spy_name="spy_1", payload=SpyCall(args=(), kwargs={})) - call_2 = SpyEvent(spy_id=101, spy_name="spy_1", payload=SpyCall(args=(), kwargs={})) - call_3 = SpyEvent(spy_id=202, spy_name="spy_2", payload=SpyCall(args=(), kwargs={})) + call_1 = SpyEvent( + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(), kwargs={}) + ) + call_2 = SpyEvent( + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(), kwargs={}) + ) + call_3 = SpyEvent( + spy=SpyInfo(id=202, name="spy_2"), payload=SpyCall(args=(), kwargs={}) + ) subject.push(call_1) subject.consume_when_rehearsal(ignore_extra_args=False) @@ -334,25 +325,31 @@ def test_get_all() -> None: assert subject.get_all() == [ WhenRehearsal( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_1", + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(), kwargs={}), ), - SpyEvent(spy_id=202, spy_name="spy_2", payload=SpyCall(args=(), kwargs={})), + SpyEvent( + spy=SpyInfo(id=202, name="spy_2"), payload=SpyCall(args=(), kwargs={}) + ), ] def test_clear() -> None: """It can clear all calls and rehearsals.""" subject = SpyLog() - call_1 = SpyEvent(spy_id=101, spy_name="spy_1", payload=SpyCall(args=(), kwargs={})) - call_2 = SpyEvent(spy_id=101, spy_name="spy_1", payload=SpyCall(args=(), kwargs={})) - call_3 = SpyEvent(spy_id=202, spy_name="spy_2", payload=SpyCall(args=(), kwargs={})) + call_1 = SpyEvent( + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(), kwargs={}) + ) + call_2 = SpyEvent( + spy=SpyInfo(id=101, name="spy_1"), payload=SpyCall(args=(), kwargs={}) + ) + call_3 = SpyEvent( + spy=SpyInfo(id=202, name="spy_2"), payload=SpyCall(args=(), kwargs={}) + ) subject.push(call_1) subject.consume_when_rehearsal(ignore_extra_args=False) diff --git a/tests/test_stringify.py b/tests/test_stringify.py index 47b7763..2cb81f5 100644 --- a/tests/test_stringify.py +++ b/tests/test_stringify.py @@ -2,7 +2,7 @@ import pytest from typing import NamedTuple -from decoy.spy_events import SpyCall, SpyEvent, SpyPropAccess, PropAccessType +from decoy.spy_events import SpyCall, SpyEvent, SpyInfo, SpyPropAccess, PropAccessType from decoy.stringify import stringify_call @@ -16,34 +16,35 @@ class StringifyCallSpec(NamedTuple): stringify_call_specs = [ StringifyCallSpec( call=SpyEvent( - spy_id=42, spy_name="some.name", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="some.name"), + payload=SpyCall(args=(), kwargs={}), ), expected="some.name()", ), StringifyCallSpec( call=SpyEvent( - spy_id=42, spy_name="some.name", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=42, name="some.name"), + payload=SpyCall(args=(1,), kwargs={}), ), expected="some.name(1)", ), StringifyCallSpec( call=SpyEvent( - spy_id=42, spy_name="some.name", payload=SpyCall(args=(1, "2"), kwargs={}) + spy=SpyInfo(id=42, name="some.name"), + payload=SpyCall(args=(1, "2"), kwargs={}), ), expected="some.name(1, '2')", ), StringifyCallSpec( call=SpyEvent( - spy_id=42, - spy_name="some.name", + spy=SpyInfo(id=42, name="some.name"), payload=SpyCall(args=(), kwargs={"foo": "bar"}), ), expected="some.name(foo='bar')", ), StringifyCallSpec( call=SpyEvent( - spy_id=42, - spy_name="some.name", + spy=SpyInfo(id=42, name="some.name"), payload=SpyCall( args=(1, 2), kwargs={"foo": "bar", "baz": False}, @@ -53,8 +54,7 @@ class StringifyCallSpec(NamedTuple): ), StringifyCallSpec( call=SpyEvent( - spy_id=42, - spy_name="some.name", + spy=SpyInfo(id=42, name="some.name"), payload=SpyCall( args=(), kwargs={}, @@ -65,16 +65,14 @@ class StringifyCallSpec(NamedTuple): ), StringifyCallSpec( call=SpyEvent( - spy_id=42, - spy_name="some", + spy=SpyInfo(id=42, name="some"), payload=SpyPropAccess(prop_name="name", access_type=PropAccessType.GET), ), expected="some.name", ), StringifyCallSpec( call=SpyEvent( - spy_id=42, - spy_name="some", + spy=SpyInfo(id=42, name="some"), payload=SpyPropAccess( prop_name="name", access_type=PropAccessType.SET, @@ -85,8 +83,7 @@ class StringifyCallSpec(NamedTuple): ), StringifyCallSpec( call=SpyEvent( - spy_id=42, - spy_name="some", + spy=SpyInfo(id=42, name="some"), payload=SpyPropAccess(prop_name="name", access_type=PropAccessType.DELETE), ), expected="del some.name", diff --git a/tests/test_stub_store.py b/tests/test_stub_store.py index 5f49acb..e1e7f02 100644 --- a/tests/test_stub_store.py +++ b/tests/test_stub_store.py @@ -1,7 +1,7 @@ """Tests for stub behavior storage.""" import pytest -from decoy.spy_events import SpyCall, SpyEvent, WhenRehearsal +from decoy.spy_events import SpyCall, SpyEvent, SpyInfo, WhenRehearsal from decoy.stub_store import StubStore, StubBehavior @@ -9,13 +9,15 @@ def test_get_by_call() -> None: """It should be able to add a StubBehavior to the store and get it back.""" subject = StubStore() rehearsal = WhenRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) behavior = StubBehavior(return_value="hello world") subject.add(rehearsal=rehearsal, behavior=behavior) result = subject.get_by_call( - call=SpyEvent(spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={})) + call=SpyEvent( + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) + ) ) assert result == behavior @@ -25,18 +27,20 @@ def test_get_by_call_prefers_latest() -> None: """It should be prefer later stubs if multiple exist.""" subject = StubStore() rehearsal_1 = WhenRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) behavior_1 = StubBehavior(return_value="hello") rehearsal_2 = WhenRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) behavior_2 = StubBehavior(return_value="goodbye") subject.add(rehearsal=rehearsal_1, behavior=behavior_1) subject.add(rehearsal=rehearsal_2, behavior=behavior_2) result = subject.get_by_call( - call=SpyEvent(spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={})) + call=SpyEvent( + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) + ) ) assert result == behavior_2 @@ -46,7 +50,9 @@ def test_get_by_call_empty() -> None: """It should return None if store is empty.""" subject = StubStore() result = subject.get_by_call( - call=SpyEvent(spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={})) + call=SpyEvent( + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) + ) ) assert result is None @@ -57,16 +63,14 @@ def test_get_by_call_empty() -> None: [ SpyEvent( # spy_id does not match - spy_id=24, - spy_name="my_spy", + spy=SpyInfo(id=1000000000, name="my_spy"), payload=SpyCall( args=("hello", "world"), kwargs={"goodbye": "so long"}, ), ), SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), # args do not match payload=SpyCall( args=("hello", "wisconsin"), @@ -74,8 +78,7 @@ def test_get_by_call_empty() -> None: ), ), SpyEvent( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall( args=("hello", "wisconsin"), # kwargs do not match @@ -88,8 +91,7 @@ def test_get_by_call_no_match(call: SpyEvent) -> None: """It should return a no-op StubBehavior if there are no matching calls.""" subject = StubStore() rehearsal = WhenRehearsal( - spy_id=42, - spy_name="my_spy", + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall( args=("hello", "world"), kwargs={"goodbye": "so long"}, @@ -107,7 +109,7 @@ def test_get_by_call_once_behavior() -> None: """It should consume any behavior marked with the `once` flag.""" subject = StubStore() rehearsal = WhenRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(1, 2, 3), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(1, 2, 3), kwargs={}) ) behavior = StubBehavior(return_value="fizzbuzz", once=True) @@ -115,7 +117,8 @@ def test_get_by_call_once_behavior() -> None: result = subject.get_by_call( call=SpyEvent( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(1, 2, 3), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), + payload=SpyCall(args=(1, 2, 3), kwargs={}), ) ) @@ -123,7 +126,8 @@ def test_get_by_call_once_behavior() -> None: result = subject.get_by_call( call=SpyEvent( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(1, 2, 3), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), + payload=SpyCall(args=(1, 2, 3), kwargs={}), ) ) @@ -133,9 +137,11 @@ def test_get_by_call_once_behavior() -> None: def test_clear() -> None: """It should consume any behavior marked with the `once` flag.""" subject = StubStore() - call = SpyEvent(spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={})) + call = SpyEvent( + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) + ) rehearsal = WhenRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ) behavior = StubBehavior(return_value="fizzbuzz") diff --git a/tests/test_verifier.py b/tests/test_verifier.py index e554c17..aa766aa 100644 --- a/tests/test_verifier.py +++ b/tests/test_verifier.py @@ -2,7 +2,7 @@ import pytest from typing import List, NamedTuple, Optional -from decoy.spy_events import SpyCall, SpyEvent, VerifyRehearsal +from decoy.spy_events import SpyCall, SpyEvent, SpyInfo, VerifyRehearsal from decoy.errors import VerifyError from decoy.verifier import Verifier @@ -19,7 +19,7 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ), ], calls=[], @@ -27,18 +27,16 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), payload=SpyCall(args=(), kwargs={}) ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), ], @@ -46,35 +44,29 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), VerifyRehearsal( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=(7, 8, 9), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), SpyEvent( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=("oh no",), kwargs={}), ), ], @@ -82,30 +74,25 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), VerifyRehearsal( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=(7, 8, 9), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), ], @@ -113,20 +100,17 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], @@ -135,20 +119,17 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], @@ -160,47 +141,43 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(1, 2, 3), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), + payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=42, spy_name="my_spy", payload=SpyCall(args=(1, 2, 3), kwargs={}) + spy=SpyInfo(id=42, name="my_spy"), + payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], ), VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), VerifyRehearsal( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=(7, 8, 9), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), SpyEvent( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=(7, 8, 9), kwargs={}), ), ], @@ -208,40 +185,33 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), VerifyRehearsal( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=(7, 8, 9), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(0, 0, 0), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), SpyEvent( - spy_id=202, - spy_name="spy_202", + spy=SpyInfo(id=202, name="spy_202"), payload=SpyCall(args=(7, 8, 9), kwargs={}), ), ], @@ -249,20 +219,17 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], @@ -271,20 +238,17 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(4, 5, 6), kwargs={}), ), SpyEvent( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], @@ -293,8 +257,7 @@ class VerifySpec(NamedTuple): VerifySpec( rehearsals=[ VerifyRehearsal( - spy_id=101, - spy_name="spy_101", + spy=SpyInfo(id=101, name="spy_101"), payload=SpyCall(args=(1, 2, 3), kwargs={}), ), ], diff --git a/tests/test_warning_checker.py b/tests/test_warning_checker.py index e931057..50a3e28 100644 --- a/tests/test_warning_checker.py +++ b/tests/test_warning_checker.py @@ -7,6 +7,7 @@ AnySpyEvent, SpyCall, SpyEvent, + SpyInfo, SpyPropAccess, PropAccessType, WhenRehearsal, @@ -33,16 +34,20 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), ], expected_warnings=[], ), # it should not warn if a call is made and there are no rehearsals WarningCheckerSpec( all_calls=[ - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) + ), ], expected_warnings=[], ), @@ -50,20 +55,24 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), ], expected_warnings=[ MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(), kwargs={}), ) ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(1,), kwargs={}), ) ], ) @@ -73,9 +82,11 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ VerifyRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), ], expected_warnings=[], ), @@ -83,22 +94,19 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, - spy_name="spy", + spy=SpyInfo(id=1, name="spy"), payload=SpyPropAccess( prop_name="prop_name", access_type=PropAccessType.GET ), ), SpyEvent( - spy_id=1, - spy_name="spy", + spy=SpyInfo(id=1, name="spy"), payload=SpyPropAccess( prop_name="other_prop", access_type=PropAccessType.GET ), ), WhenRehearsal( - spy_id=2, - spy_name="spy.other_prop", + spy=SpyInfo(id=2, name="spy.other_prop"), payload=SpyCall(args=(), kwargs={}, ignore_extra_args=False), ), ], @@ -108,26 +116,31 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) ), WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(0,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(0,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), ], expected_warnings=[ MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(), kwargs={}), ), WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(0,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(0,), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(1,), kwargs={}), ), ], ) @@ -137,21 +150,27 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=2, name="yps"), payload=SpyCall(args=(2,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), - SpyEvent(spy_id=2, spy_name="yps", payload=SpyCall(args=(2,), kwargs={})), ], expected_warnings=[ MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(1,), kwargs={}), ), ], ) @@ -161,23 +180,27 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), ], expected_warnings=[ MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(1,), kwargs={}), ), ], ) @@ -187,36 +210,44 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) ), WhenRehearsal( - spy_id=2, spy_name="yps", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=2, name="yps"), payload=SpyCall(args=(1,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=2, name="yps"), payload=SpyCall(args=(), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), - SpyEvent(spy_id=2, spy_name="yps", payload=SpyCall(args=(), kwargs={})), ], expected_warnings=[ MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(1,), kwargs={}), ), ], ), MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=2, spy_name="yps", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=2, name="yps"), + payload=SpyCall(args=(1,), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=2, spy_name="yps", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=2, name="yps"), + payload=SpyCall(args=(), kwargs={}), ), ], ), @@ -227,43 +258,55 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(2,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={})), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={})), WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(3,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(3,), kwargs={})), ], expected_warnings=[ MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(1,), kwargs={}), ), SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(2,), kwargs={}), ), ], ), MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(), kwargs={}), ), WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(1,), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(3,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(3,), kwargs={}), ), ], ), @@ -273,11 +316,13 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(2,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={})), VerifyRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(2,), kwargs={}) ), ], expected_warnings=[], @@ -285,25 +330,31 @@ class WarningCheckerSpec(NamedTuple): # it should warn if a call misses a stubbing after it is verified WarningCheckerSpec( all_calls=[ - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={})), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(2,), kwargs={}) + ), VerifyRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(2,), kwargs={}) ), WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) + ), + SpyEvent( + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(2,), kwargs={}) ), - SpyEvent(spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={})), ], expected_warnings=[ MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(1,), kwargs={}), ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), + payload=SpyCall(args=(2,), kwargs={}), ), ], ), @@ -313,17 +364,16 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), VerifyRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), ], expected_warnings=[ RedundantVerifyWarning( rehearsal=VerifyRehearsal( - spy_id=1, - spy_name="spy", + spy=SpyInfo(id=1, name="spy"), payload=SpyCall( args=(1,), kwargs={}, @@ -336,10 +386,10 @@ class WarningCheckerSpec(NamedTuple): WarningCheckerSpec( all_calls=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), VerifyRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(2,), kwargs={}) ), ], expected_warnings=[], diff --git a/tests/test_warnings.py b/tests/test_warnings.py index f79232f..78cc9bf 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -3,7 +3,7 @@ import os from typing import NamedTuple -from decoy.spy_events import SpyCall, SpyEvent, WhenRehearsal, VerifyRehearsal +from decoy.spy_events import SpyCall, SpyEvent, SpyInfo, WhenRehearsal, VerifyRehearsal from decoy.warnings import DecoyWarning, MiscalledStubWarning, RedundantVerifyWarning @@ -19,12 +19,12 @@ class WarningSpec(NamedTuple): warning=MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), ], ), @@ -42,15 +42,15 @@ class WarningSpec(NamedTuple): warning=MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) ), WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(0,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(0,), kwargs={}) ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), ], ), @@ -69,15 +69,15 @@ class WarningSpec(NamedTuple): warning=MiscalledStubWarning( rehearsals=[ WhenRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(), kwargs={}) ), ], calls=[ SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), SpyEvent( - spy_id=1, spy_name="spy", payload=SpyCall(args=(2,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(2,), kwargs={}) ), ], ), @@ -95,7 +95,7 @@ class WarningSpec(NamedTuple): WarningSpec( warning=RedundantVerifyWarning( rehearsal=VerifyRehearsal( - spy_id=1, spy_name="spy", payload=SpyCall(args=(1,), kwargs={}) + spy=SpyInfo(id=1, name="spy"), payload=SpyCall(args=(1,), kwargs={}) ), ), expected_message=os.linesep.join(