Skip to content

Commit

Permalink
Merge 3e19346 into 276dccc
Browse files Browse the repository at this point in the history
  • Loading branch information
macisamuele committed Feb 21, 2021
2 parents 276dccc + 3e19346 commit 7fe5b74
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 38 deletions.
13 changes: 5 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ endif
.PHONY: all
all: tests coverage_report docs sdist

# .PHONY does not work for implicit rules, so we FORCE them
FORCE:

##
## Docs
##
Expand All @@ -57,7 +54,7 @@ docs_clean:
## Tests
##

%_unittest.py: FORCE coverage_erase
%_unittest.py: coverage_erase
@printf "${TERM_BRIGHT}UNITTEST $@\n${TERM_NONE}"
${Q} coverage run \
-m unittest \
Expand All @@ -70,15 +67,15 @@ unittest_tests: $(TESTS_SRCS)/*_unittest.py

.PHONY: pytest_tests
pytest_tests: export PYTHONPATH=${CURDIR}/pytest-testslide:${CURDIR}
pytest_tests: FORCE coverage_erase
pytest_tests: coverage_erase
@printf "${TERM_BRIGHT}INSTALL pytest_testslide DEPS ${TERM_NONE}\n"
${Q} pip install -r pytest-testslide/requirements.txt
@printf "${TERM_BRIGHT}PYTEST pytest_testslide${TERM_NONE}\n"
${Q} coverage run \
-m pytest \
pytest-testslide/tests
-m pytest \
pytest-testslide/tests

%_testslide.py: FORCE coverage_erase
%_testslide.py: coverage_erase
@printf "${TERM_BRIGHT}TESTSLIDE $@\n${TERM_NONE}"
${Q} coverage run \
-m testslide.cli \
Expand Down
2 changes: 1 addition & 1 deletion pytest-testslide/tests/test_pytest_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async def test_mock_async_callable_patching_works(testslide):
async def test_mock_async_callable_unpatching_works(testslide):
# This will fail if unpatching from test_mock_async_callable_patching_works does
# not happen
assert await sample_module.ParentTarget.async_static_method("a", "b") == "async original response"
assert await sample_module.ParentTarget.async_static_method("a", "b") == ["async original response"]
@pytest.mark.asyncio
async def test_mock_async_callable_assertion_works(testslide):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
packages=["testslide"],
maintainer="Fabio Pugliese Ornellas",
maintainer_email="fabio.ornellas@gmail.com",
url="https://github.com/facebookincubator/TestSlide",
url="https://github.com/facebook/TestSlide",
license="MIT",
description="A test framework for Python that makes mocking and iterating over code with tests a breeze",
long_description=readme,
Expand Down
128 changes: 126 additions & 2 deletions tests/mock_callable_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,7 +922,7 @@ def class_is_not_mocked(self):
getattr(sample_module.Target, self.callable_arg)(
*self.call_args, **self.call_kwargs
),
"original response",
["original response"],
)

@context.shared_context
Expand Down Expand Up @@ -1245,7 +1245,7 @@ def other_instances_are_not_mocked(self):
getattr(sample_module.Target(), self.callable_arg)(
*self.call_args, **self.call_kwargs
),
"original response",
["original response"],
)

@context.sub_context
Expand Down Expand Up @@ -1573,3 +1573,127 @@ def _original_target(self):
can_yield=False,
)
context.merge_context("other instances are not mocked")

@context.sub_context
def mock_sync_async_callable_type_check_errors(context):
@context.shared_context
def run_context(context, target):
@context.example
def mock_callable_to_return_value(self):
self.mock_callable(target, "instance_method").to_return_value(
1
).and_assert_called()

# instance_method is annotated to return a string and here we would retrieve 1
with self.assertRaises(TypeCheckError):
target.instance_method(
arg1="arg1", arg2="arg2", kwarg1="kwarg1", kwarg2="kwarg2"
)

@context.example
def mock_callable_to_return_values(self):
self.mock_callable(target, "instance_method").to_return_values(
[1, 2, "ok"]
).and_assert_called()

# instance_method is annotated to return a string and here we would retrieve 1
with self.assertRaises(TypeCheckError):
target.instance_method(
arg1="arg1", arg2="arg2", kwarg1="kwarg1", kwarg2="kwarg2"
)

# instance_method is annotated to return a string and here we would retrieve 2
with self.assertRaises(TypeCheckError):
target.instance_method(
arg1="arg1", arg2="arg2", kwarg1="kwarg1", kwarg2="kwarg2"
)

target.instance_method(
arg1="arg1", arg2="arg2", kwarg1="kwarg1", kwarg2="kwarg2"
)

@context.example
def mock_callable_with_implementation(self):
self.mock_callable(target, "instance_method").with_implementation(
lambda arg1, **kwargs: 1 if arg1 == "give_me_an_int" else arg1
).and_assert_called()

# instance_method is annotated to return a string and here we would retrieve an integer
with self.assertRaises(TypeCheckError):
target.instance_method(
arg1="give_me_an_int",
arg2="arg2",
kwarg1="kwarg1",
kwarg2="kwarg2",
)

target.instance_method(
arg1="arg1", arg2="arg2", kwarg1="kwarg1", kwarg2="kwarg2"
)

@context.example
def mock_async_callable_to_return_value(self):
self.mock_async_callable(
target, "async_instance_method"
).to_return_value(1).and_assert_called()

# instance_method is annotated to return a string and here we would retrieve 1
with self.assertRaises(TypeCheckError):
self.async_run_with_health_checks(
target.async_instance_method(
arg1="arg1", arg2="arg2", kwarg1="kwarg1", kwarg2="kwarg2"
)
)

@context.example
def mock_async_callable_to_return_values(self):
self.mock_async_callable(
target, "async_instance_method"
).to_return_values([1, 2, "ok"]).and_assert_called()

# instance_method is annotated to return a string and here we would retrieve 1
with self.assertRaises(TypeCheckError):
self.async_run_with_health_checks(
target.async_instance_method(
arg1="arg1", arg2="arg2", kwarg1="kwarg1", kwarg2="kwarg2"
)
)

# instance_method is annotated to return a string and here we would retrieve 2
with self.assertRaises(TypeCheckError):
self.async_run_with_health_checks(
target.async_instance_method("arg1", "arg2")
)

self.async_run_with_health_checks(
target.async_instance_method("arg1", "arg2")
)

@context.example
def mock_async_callable_with_implementation(self):
async def impl(arg1: str, arg2: str, **kwargs):
return 1 if arg1 == "give_me_an_int" else arg1

self.mock_async_callable(
target, "async_instance_method"
).with_implementation(impl).and_assert_called()

# instance_method is annotated to return a string and here we would retrieve an integer
with self.assertRaises(TypeCheckError):
self.async_run_with_health_checks(
target.async_instance_method("give_me_an_int", "arg2")
)

self.async_run_with_health_checks(
target.async_instance_method("arg1", "arg2")
)

@context.sub_context
def using_concrete_instance(context):
context.merge_context("run context", target=sample_module.Target())

@context.sub_context
def using_strict_mock(context):
context.merge_context(
"run context", target=StrictMock(sample_module.ParentTarget)
)
46 changes: 24 additions & 22 deletions tests/sample_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

from typing import Dict, Optional, Tuple, Union
from typing import Any, Dict, Iterable, Optional, Tuple, Union

attribute = "value"
typedattr: str = "bruh"
Expand Down Expand Up @@ -33,45 +33,47 @@ def instance_method_with_star_args(


class TargetStr(object):
def __str__(self):
def __str__(self) -> str:
return "original response"

def _privatefun(self):
def _privatefun(self) -> str:
return "cannotbemocked"


class ParentTarget(TargetStr):
def instance_method(
self, arg1: str, arg2: str, kwarg1: str = "", kwarg2: str = ""
) -> str:
return "original response"
) -> Iterable[str]:
return ["original response"]

async def async_instance_method(
self, arg1: str, arg2: str, kwarg1: str = "", kwarg2: str = ""
) -> str:
return "async original response"
) -> Iterable[str]:
return ["async original response"]

@staticmethod
def static_method(arg1: str, arg2: str, kwarg1: str = "", kwarg2: str = "") -> str:
return "original response"
def static_method(
arg1: str, arg2: str, kwarg1: str = "", kwarg2: str = ""
) -> Iterable[str]:
return ["original response"]

@staticmethod
async def async_static_method(
arg1: str, arg2: str, kwarg1: str = "", kwarg2: str = ""
) -> str:
return "async original response"
) -> Iterable[str]:
return ["async original response"]

@classmethod
def class_method(
cls, arg1: str, arg2: str, kwarg1: str = "", kwarg2: str = ""
) -> str:
return "original response"
) -> Iterable[str]:
return ["original response"]

@classmethod
async def async_class_method(
cls, arg1: str, arg2: str, kwarg1: str = "", kwarg2: str = ""
) -> str:
return "async original response"
) -> Iterable[str]:
return ["async original response"]

async def __aiter__(self):
return self
Expand All @@ -85,24 +87,24 @@ def __init__(self):
super(Target, self).__init__()

@property
def invalid(self):
def invalid(self) -> None:
"""
Covers a case where create_autospec at an instance would fail.
"""
raise RuntimeError("Should not be accessed")


class CallOrderTarget(object):
def __init__(self, name):
def __init__(self, name: str) -> None:
self.name = name

def __repr__(self):
def __repr__(self) -> str:
return self.name

def f1(self, arg):
def f1(self, arg: Any) -> str:
return "f1: {}".format(repr(arg))

def f2(self, arg):
def f2(self, arg: Any) -> str:
return "f2: {}".format(repr(arg))


Expand All @@ -121,12 +123,12 @@ async def async_test_function(
UnionArgType = Dict[str, Union[str, int]]


def test_union(arg: UnionArgType):
def test_union(arg: UnionArgType) -> None:
pass


TupleArgType = Dict[str, Tuple[str, int]]


def test_tuple(arg: TupleArgType):
def test_tuple(arg: TupleArgType) -> None:
pass
3 changes: 3 additions & 0 deletions testslide/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import sys
import unittest.mock
from functools import wraps
from inspect import Traceback
from types import FrameType
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Tuple, Type, Union
Expand Down Expand Up @@ -271,6 +272,8 @@ def _wrap_signature_and_type_validation(

skip_first_arg = _skip_first_arg(template, attr_name)

# Add this so docstrings and method name are not altered by the mock
@wraps(callable_template)
def with_sig_and_type_validation(*args: Any, **kwargs: Any) -> Any:
if _validate_callable_signature(
skip_first_arg, callable_template, template, attr_name, args, kwargs
Expand Down
7 changes: 3 additions & 4 deletions testslide/mock_callable.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,10 +729,9 @@ def _patch(
else:
original_callable = getattr(self._target, self._method)

if not isinstance(self._target, StrictMock):
new_value = _wrap_signature_and_type_validation(
new_value, self._target, self._method, self.type_validation
)
new_value = _wrap_signature_and_type_validation(
new_value, self._target, self._method, self.type_validation
)

restore = self._method in self._target.__dict__
restore_value = self._target.__dict__.get(self._method, None)
Expand Down

0 comments on commit 7fe5b74

Please sign in to comment.