Skip to content

Commit

Permalink
Add mock_callable and mock_async_callable support for slotted classes (
Browse files Browse the repository at this point in the history
…#355)

Summary:
Pull Request resolved: #355

This PR has the objective of supporting `self.mock_callable` and `self.mock_async_callable` for methods of slotted classes.

Before the PR mocking was not possible as `testslide` implementation assumed the presence of `__dict__` (which cannot be ensured on slotted classes).

## Test plan
```
(venv) maci:testslide/ (maci-add-support-for-slotted-classes) $ git co -f HEAD~1 testslide
Updated 1 path from ad55cd4
(venv) maci:testslide/ (maci-add-support-for-slotted-classes✗) $ git reset
Unstaged changes after reset:
M	testslide/mock_callable.py
(venv) maci:testslide/ (maci-add-support-for-slotted-classes✗) $ git diff
(venv) maci:testslide/ (maci-add-support-for-slotted-classes✗) $ git diff | cat
 diff --git a/testslide/mock_callable.py b/testslide/mock_callable.py
index 12fc9c9..87cf2f4 100644
 --- a/testslide/mock_callable.py
+++ b/testslide/mock_callable.py
@@ -776,14 +776,8 @@ class _MockCallableDSL:
             self.type_validation or self.type_validation is None,
         )

-        if isinstance(self._target, (Mock, StrictMock)) or not hasattr(
-            self._target, "__slots__"
-        ):
-            restore = self._method in self._target.__dict__
-            restore_value = self._target.__dict__.get(self._method, None)
-        else:
-            restore = self._method in self._target.__slots__
-            restore_value = getattr(self._target, self._method)
+        restore = self._method in self._target.__dict__
+        restore_value = self._target.__dict__.get(self._method, None)

         if inspect.isclass(self._target):
             new_value = staticmethod(new_value)  # type: ignore
(venv) maci:testslide/ (maci-add-support-for-slotted-classes✗) $ make tests/mock_callable_testslide.py
COVERAGE ERASE
TESTSLIDE tests/mock_callable_testslide.py
mock_callable()
  patching private functions raises valueerror
  patching private functions with allow private
  patching functions in slotted class: AttributeError: 'SomeClassWithSlots' object has no attribute '__dict__'

Failures:

  1) mock_callable(): patching functions in slotted class
    1) AttributeError: 'SomeClassWithSlots' object has no attribute '__dict__'
      File "testslide/runner.py", line 835, in run
        self._run_example(example)
      File "testslide/runner.py", line 817, in _run_example
        _ExampleRunner(
      File "testslide/__init__.py", line 534, in run
        self._sync_run_all_hooks_and_example(context_data)
      File "testslide/__init__.py", line 506, in _sync_run_all_hooks_and_example
        aggregated_exceptions.raise_correct_exception()
      File "testslide/__init__.py", line 275, in raise_correct_exception
        raise self.exceptions[0]
      File "testslide/__init__.py", line 259, in catch
        yield
      File "testslide/__init__.py", line 497, in _sync_run_all_hooks_and_example
        self._fail_if_coroutine_function(self.example.code, context_data)
      File "testslide/__init__.py", line 465, in _fail_if_coroutine_function
        return func(*args, **kwargs)
      File "tests/mock_callable_testslide.py", line 110, in patching_functions_in_slotted_class
        self.mock_callable(t, "method").to_return_value(42).and_assert_called_once()
      File "testslide/mock_callable.py", line 52, in mock_callable
        return _MockCallableDSL(
      File "testslide/mock_callable.py", line 851, in __init__
        original_callable, unpatcher = self._patch(callable_mock)
      File "testslide/mock_callable.py", line 779, in _patch
        restore = self._method in self._target.__dict__

Executed 3 examples in 0.4s:
  Successful: 2
  Failed: 1
  Skipped: 0
  Not executed: 2392
https://testslide.readthedocs.io/
make: *** [tests/mock_callable_testslide.py] Error 1
(venv) maci:testslide/ (maci-add-support-for-slotted-classes✗) $ make tests/mock_async_callable_testslide.py
COVERAGE ERASE
TESTSLIDE tests/mock_async_callable_testslide.py
mock_async_callable()
  patching functions in slotted class: AttributeError: 'SomeClassWithSlots' object has no attribute '__dict__'

Failures:

  1) mock_async_callable(): patching functions in slotted class
    1) AttributeError: 'SomeClassWithSlots' object has no attribute '__dict__'
      File "testslide/runner.py", line 835, in run
        self._run_example(example)
      File "testslide/runner.py", line 817, in _run_example
        _ExampleRunner(
      File "testslide/__init__.py", line 532, in run
        self._async_run_all_hooks_and_example(context_data)
      File "testslide/__init__.py", line 457, in _async_run_all_hooks_and_example
        asyncio_run(coro)
      File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/asyncio/runners.py", line 44, in run
        return loop.run_until_complete(main)
      File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
        return future.result()
      File "testslide/__init__.py", line 370, in _real_async_run_all_hooks_and_example
        aggregated_exceptions.raise_correct_exception()
      File "testslide/__init__.py", line 275, in raise_correct_exception
        raise self.exceptions[0]
      File "testslide/__init__.py", line 259, in catch
        yield
      File "testslide/__init__.py", line 356, in _real_async_run_all_hooks_and_example
        await _async_ensure_no_leaked_tasks(
      File "testslide/__init__.py", line 93, in _async_ensure_no_leaked_tasks
        result = await coro
      File "testslide/__init__.py", line 322, in _fail_if_not_coroutine_function
        return await func(*args, **kwargs)
      File "tests/mock_async_callable_testslide.py", line 96, in patching_functions_in_slotted_class
        self.mock_async_callable(t, "async_method").to_return_value(
      File "testslide/mock_callable.py", line 72, in mock_async_callable
        return _MockAsyncCallableDSL(
      File "testslide/mock_callable.py", line 1179, in __init__
        super().__init__(
      File "testslide/mock_callable.py", line 851, in __init__
        original_callable, unpatcher = self._patch(callable_mock)
      File "testslide/mock_callable.py", line 779, in _patch
        restore = self._method in self._target.__dict__

Executed 1 example in 0.1s:
  Successful: 0
  Failed: 1
  Skipped: 0
  Not executed: 263
https://testslide.readthedocs.io/
make: *** [tests/mock_async_callable_testslide.py] Error 1
(venv) maci:testslide/ (maci-add-support-for-slotted-classes✗) $ git co -f .
Updated 1 path from the index
(venv) maci:testslide/ (maci-add-support-for-slotted-classes) $ git diff | cat
(venv) maci:testslide/ (maci-add-support-for-slotted-classes) $ make tests/mock_callable_testslide.py
COVERAGE ERASE
TESTSLIDE tests/mock_callable_testslide.py
(venv) maci:testslide/ (maci-add-support-for-slotted-classes) $ make tests/mock_async_callable_testslide.py
COVERAGE ERASE
TESTSLIDE tests/mock_async_callable_testslide.py
(venv) maci:testslide/ (maci-add-support-for-slotted-classes) $
```

All tests passes
```
maci:testslide/ (maci-add-support-for-slotted-classes) $ make ci
CREATE VIRTUALENV (/Users/maci/github/testslide/venv)
INSTALL BUILD DEPS
INSTALL DEPS
COVERAGE ERASE
UNITTEST tests/accept_any_arg_unittest.py
UNITTEST tests/cli_unittest.py
UNITTEST tests/dsl_unittest.py
UNITTEST tests/matchers_unittest.py
UNITTEST tests/pep487_unittest.py
UNITTEST tests/testcase_unittest.py
TESTSLIDE tests/lib_testslide.py
TESTSLIDE tests/mock_async_callable_testslide.py
TESTSLIDE tests/mock_callable_testslide.py
TESTSLIDE tests/mock_constructor_testslide.py
TESTSLIDE tests/patch_attribute_testslide.py
TESTSLIDE tests/strict_mock_testslide.py
INSTALL pytest_testslide DEPS
PYTEST pytest_testslide
MYPY tests testslide util pytest-testslide
FLAKE8 tests testslide util pytest-testslide
ISORT tests testslide util pytest-testslide
BLACK tests testslide util pytest-testslide
Copyright structure intact for all python files
COVERAGE COMBINE
COVERAGE REPORT
DOCS
SDIST
INSTALL LOCAL
maci:testslide/ (maci-add-support-for-slotted-classes) $
```
**Checklist**:
- [x] Added tests, if you've added code that should be tested
- [ ] Updated the documentation, if you've changed APIs
- [x] Ensured the test suite passes
- [x] Made sure your code lints
- [x] Completed the Contributor License Agreement ("CLA")

Reviewed By: deathowl

Differential Revision: D46856794

fbshipit-source-id: 747657bcfcace554da079b3403968052dceefe04
  • Loading branch information
macisamuele authored and facebook-github-bot committed Jun 20, 2023
1 parent de9e2c1 commit 6b938e7
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 2 deletions.
8 changes: 8 additions & 0 deletions tests/mock_async_callable_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ async def cleanup_patches(self):
## Shared Contexts
##

@context.example
async def patching_functions_in_slotted_class(self):
t = sample_module.SomeClassWithSlots(attribute="value")
self.mock_async_callable(t, "async_method").to_return_value(
42
).and_assert_called_once()
self.assertEqual(await t.async_method(), 42)

@context.shared_context
def mock_async_callable_with_sync_examples(context, can_mock_with_flag=True):
@context.example
Expand Down
6 changes: 6 additions & 0 deletions tests/mock_callable_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ def patching_private_functions_with_allow_private(self):
).and_assert_called_once()
t._privatefun()

@context.example
def patching_functions_in_slotted_class(self):
t = sample_module.SomeClassWithSlots(attribute="value")
self.mock_callable(t, "method").to_return_value(42).and_assert_called_once()
self.assertEqual(t.method(), 42)

##
## Shared Contexts
##
Expand Down
13 changes: 13 additions & 0 deletions tests/sample_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ def instance_method_with_star_args(
return 3


class SomeClassWithSlots:
__slots__ = ("attribute",)

def __init__(self, attribute: str) -> None:
self.attribute = attribute

def method(self) -> int:
return 0

async def async_method(self) -> int:
return 0


class TargetStr:
def __str__(self) -> str:
return "original response"
Expand Down
10 changes: 8 additions & 2 deletions testslide/mock_callable.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,8 +776,14 @@ def _patch(
self.type_validation or self.type_validation is None,
)

restore = self._method in self._target.__dict__
restore_value = self._target.__dict__.get(self._method, None)
if isinstance(self._target, (Mock, StrictMock)) or not hasattr(
self._target, "__slots__"
):
restore = self._method in self._target.__dict__
restore_value = self._target.__dict__.get(self._method, None)
else:
restore = self._method in self._target.__slots__
restore_value = getattr(self._target, self._method)

if inspect.isclass(self._target):
new_value = staticmethod(new_value) # type: ignore
Expand Down

0 comments on commit 6b938e7

Please sign in to comment.