-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add async method mock #148
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -16,10 +16,8 @@ jobs: | |||||
matrix: | ||||||
python-version: | ||||||
[ | ||||||
"pypy-3.7", | ||||||
"pypy-3.8", | ||||||
"pypy-3.9", | ||||||
"3.7", | ||||||
"3.8", | ||||||
"3.9", | ||||||
"3.10", | ||||||
|
@@ -57,7 +55,7 @@ jobs: | |||||
- name: Set up Python | ||||||
uses: actions/setup-python@v4 | ||||||
with: | ||||||
python-version: "3.7" | ||||||
python-version: "3.11" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tests are meant to be executed with the lowest Python version we support. Otherwise
Suggested change
|
||||||
cache: "poetry" | ||||||
|
||||||
- name: Install dependencies | ||||||
|
@@ -89,7 +87,7 @@ jobs: | |||||
- name: Set up Python | ||||||
uses: actions/setup-python@v4 | ||||||
with: | ||||||
python-version: "3.7" | ||||||
python-version: "3.11" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
cache: "poetry" | ||||||
|
||||||
- name: Install dependencies | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -21,7 +21,7 @@ jobs: | |||||
- name: Set up Python | ||||||
uses: actions/setup-python@v4 | ||||||
with: | ||||||
python-version: "3.7" | ||||||
python-version: "3.11" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
cache: "poetry" | ||||||
|
||||||
- name: Install dependencies | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,11 +65,10 @@ pip install flexmock | |
|
||
Tested to work with: | ||
|
||
- Python 3.6 | ||
- Python 3.7 | ||
- Python 3.8 | ||
- Python 3.9 | ||
- Python 3.10 | ||
- Python 3.11 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can also add Python 3.12 here. |
||
- PyPy3 | ||
|
||
Automatically integrates with all major test runners, including: | ||
|
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -18,7 +18,6 @@ classifiers = [ | |||||
"Intended Audience :: Developers", | ||||||
"Development Status :: 5 - Production/Stable", | ||||||
"Programming Language :: Python :: 3", | ||||||
"Programming Language :: Python :: 3.7", | ||||||
"Programming Language :: Python :: 3.8", | ||||||
"Programming Language :: Python :: 3.9", | ||||||
"Programming Language :: Python :: 3.10", | ||||||
|
@@ -49,7 +48,7 @@ packages = [{ include = "flexmock", from = "src" }] | |||||
"Issue Tracker" = "https://github.com/flexmock/flexmock/issues" | ||||||
|
||||||
[tool.poetry.dependencies] | ||||||
python = "^3.7.1" | ||||||
python = "^3.8.1" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
[tool.poetry.group.dev.dependencies] | ||||||
pytest = "*" | ||||||
|
@@ -59,6 +58,7 @@ black = "*" | |||||
isort = "*" | ||||||
tox = "*" | ||||||
Twisted = "*" | ||||||
pytest-asyncio = "*" | ||||||
pytest-cov = "*" | ||||||
mkdocs-material = "*" | ||||||
markdown-include = "*" | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -42,7 +42,7 @@ | |||||
"Topic :: Software Development :: Testing :: Unit", | ||||||
"Typing :: Typed", | ||||||
], | ||||||
python_requires=">=3.7.1,<4.0.0", | ||||||
python_requires=">=3.8.1,<4.0.0", | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
packages=["flexmock"], | ||||||
package_dir={"": "src"}, | ||||||
include_package_data=True, | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -28,6 +28,24 @@ | |||||||||
RE_TYPE = type(re.compile("")) | ||||||||||
|
||||||||||
|
||||||||||
async def future_raise(anything: type[BaseException]) -> None: | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we don't need these functions to be part of the public API?
Suggested change
|
||||||||||
"""Raises the given exception in a a coroutine. | ||||||||||
|
||||||||||
Args: | ||||||||||
anything: an exception to be raised when the coroutine is resolved | ||||||||||
""" | ||||||||||
raise anything | ||||||||||
|
||||||||||
|
||||||||||
async def future(anything: Any) -> Any: | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
"""Return the given argument in a a coroutine. | ||||||||||
|
||||||||||
Args: | ||||||||||
anything: an exception to be returned when the coroutine is resolved | ||||||||||
""" | ||||||||||
return anything | ||||||||||
|
||||||||||
|
||||||||||
class ReturnValue: | ||||||||||
"""ReturnValue""" | ||||||||||
|
||||||||||
|
@@ -229,6 +247,9 @@ def new_instances(self, *args: Any) -> "Expectation": | |||||||||
|
||||||||||
def _create_expectation(self, name: str, return_value: Optional[Any] = None) -> "Expectation": | ||||||||||
expectation = self._get_or_create_expectation(name, return_value) | ||||||||||
if hasattr(self._object, name): | ||||||||||
expectation._is_async = inspect.iscoroutinefunction(getattr(self._object, name)) | ||||||||||
|
||||||||||
FlexmockContainer.add_expectation(self, expectation) | ||||||||||
|
||||||||||
if _isproperty(self._object, name): | ||||||||||
|
@@ -500,7 +521,14 @@ def _handle_matched_expectation( | |||||||||
args = return_value.value | ||||||||||
assert isinstance(args, dict) | ||||||||||
raise return_value.raises(*args["kargs"], **args["kwargs"]) | ||||||||||
if expectation._is_async: | ||||||||||
return future_raise(return_value.raises) | ||||||||||
|
||||||||||
raise return_value.raises # pylint: disable=raising-bad-type | ||||||||||
|
||||||||||
if expectation._is_async: | ||||||||||
return future(return_value.value) | ||||||||||
|
||||||||||
return return_value.value | ||||||||||
|
||||||||||
def mock_method(runtime_self: Any, *kargs: Any, **kwargs: Any) -> Any: | ||||||||||
|
@@ -589,6 +617,7 @@ def __init__( | |||||||||
self._original = original | ||||||||||
|
||||||||||
self._name = name | ||||||||||
self._is_async: bool = False | ||||||||||
self._times_called: int = 0 | ||||||||||
self._modifier: str = EXACTLY | ||||||||||
self._args: Optional[Dict[str, Any]] = None | ||||||||||
|
@@ -778,6 +807,14 @@ def _match_args(self, given_args: Any) -> bool: | |||||||||
return False | ||||||||||
return True | ||||||||||
|
||||||||||
def _verify_not_async_spy(self) -> None: | ||||||||||
"""Check if trying to assert the output of an async call.""" | ||||||||||
is_spy = self._replace_with is self.__dict__.get("_original") | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am thinking, this could potentially be extracted in a property, maybe outside of this PR what do you think @ollipa ? |
||||||||||
|
||||||||||
if self._is_async and is_spy: | ||||||||||
caller_method = inspect.stack()[1].function | ||||||||||
self.__raise(FlexmockError, caller_method + "() can not be used on an async spy") | ||||||||||
|
||||||||||
def mock(self) -> Mock: | ||||||||||
"""Return the mock associated with this expectation. | ||||||||||
|
||||||||||
|
@@ -872,6 +909,7 @@ def and_return(self, *values: Any) -> "Expectation": | |||||||||
>>> plane.passenger_count() | ||||||||||
3 | ||||||||||
""" | ||||||||||
self._verify_not_async_spy() | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need to forbid this ? It feels like we could check the result of the spied function in an asynchronous way. It could require more work that could be on us @ollipa There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could forbid it now and allow it in the future when we make an implementation for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As olippa said. For now it would not work properly so I preferred raising a clear error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's what I was suggesting, but so we could create an issue for that 👍 |
||||||||||
if not values: | ||||||||||
value = None | ||||||||||
elif len(values) == 1: | ||||||||||
|
@@ -1063,6 +1101,43 @@ def at_most(self) -> "Expectation": | |||||||||
self._modifier = AT_MOST | ||||||||||
return self | ||||||||||
|
||||||||||
def make_async(self) -> "Expectation": | ||||||||||
"""Set the return values of the expectation to coroutines | ||||||||||
Need to be set before the return value is set | ||||||||||
Comment on lines
+1105
to
+1106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Returns: | ||||||||||
Self, i.e. can be chained with other Expectation methods. | ||||||||||
|
||||||||||
Examples: | ||||||||||
>>> flexmock(plane).should_receive("fly").make_async() | ||||||||||
<flexmock._api.Expectation object at ...> | ||||||||||
>>> plane.fly() | ||||||||||
<coroutine object future at ...> | ||||||||||
""" | ||||||||||
if self._return_values: | ||||||||||
self.__raise(FlexmockError, "make_async() should be used before setting a return value") | ||||||||||
self._is_async = True | ||||||||||
|
||||||||||
return self | ||||||||||
|
||||||||||
def make_sync(self) -> "Expectation": | ||||||||||
"""Make the mocked method synchronous. | ||||||||||
Need to be set before the return value is set | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Returns: | ||||||||||
Self, i.e. can be chained with other Expectation methods. | ||||||||||
|
||||||||||
Examples: | ||||||||||
>>> flexmock(plane).should_receive("fly").make_sync() | ||||||||||
<flexmock._api.Expectation object at ...> | ||||||||||
>>> plane.fly() | ||||||||||
""" | ||||||||||
if self._return_values: | ||||||||||
self.__raise(FlexmockError, "make_sync() should be used before setting a return value") | ||||||||||
self._is_async = False | ||||||||||
|
||||||||||
return self | ||||||||||
|
||||||||||
def ordered(self) -> "Expectation": | ||||||||||
"""Makes the expectation respect the order of `should_receive` statements. | ||||||||||
|
||||||||||
|
@@ -1144,6 +1219,7 @@ def and_raise(self, exception: Type[BaseException], *args: Any, **kwargs: Any) - | |||||||||
>>> flexmock(plane).should_call("repair").and_raise(RuntimeError, "err msg") | ||||||||||
<flexmock._api.Expectation object at ...> | ||||||||||
""" | ||||||||||
self._verify_not_async_spy() | ||||||||||
if not self._callable: | ||||||||||
self.__raise(FlexmockError, "can't use and_raise() with attribute stubs") | ||||||||||
if inspect.isclass(exception): | ||||||||||
|
@@ -1206,6 +1282,7 @@ def and_yield(self, *args: Any) -> "Expectation": | |||||||||
>>> next(log) | ||||||||||
'land' | ||||||||||
""" | ||||||||||
self._verify_not_async_spy() | ||||||||||
if not self._callable: | ||||||||||
self.__raise(FlexmockError, "can't use and_yield() with attribute stubs") | ||||||||||
return self.and_return(iter(args)) | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that Pypy has released 3.10 version. You can include that here.