Skip to content

Commit

Permalink
Merge 5168477 into b5bb3cf
Browse files Browse the repository at this point in the history
  • Loading branch information
fornellas committed Jul 17, 2020
2 parents b5bb3cf + 5168477 commit 252bd92
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 41 deletions.
14 changes: 7 additions & 7 deletions docs/patching/mock_callable/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ This is particularly helpful when changes are introduced to the code: if a mocke
Type Validation
---------------

If typing annotation information is available, ``mock_callable()`` validates types of objects passing through the mock. If an invalid type is detected, it will raise ``TypeError``.
If typing annotation information is available, ``mock_callable()`` validates types of objects passing through the mock. If an invalid type is detected, it will raise ``testslide.lib.TypeCheckError``.

This feature is enabled by default. If you need to disable it (potentially due to a bug, please report!), you can do so by ``mock_callable(target, name, type_validation=False)``.

Expand All @@ -416,7 +416,7 @@ Call Argument Types

.. code-block:: python
import testslide
import testslide, testslide.lib
class SomeClass:
def some_method(self, message: str):
Expand All @@ -429,8 +429,8 @@ Call Argument Types
"mocked world"
)
self.assertEqual(some_class_instance.some_method("hello"), "mocked world")
with self.assertRaises(TypeError):
# TypeError: Call with incompatible argument types:
with self.assertRaises(testslide.lib.TypeCheckError):
# TypeCheckError: Call with incompatible argument types:
# 'message': type of message must be str; got int instead
some_class_instance.some_method(1)
Expand All @@ -439,7 +439,7 @@ Return Value Type

.. code-block:: python
import testslide
import testslide, testslide.lib
class SomeClass:
def one(self) -> int:
Expand All @@ -451,8 +451,8 @@ Return Value Type
self.mock_callable(some_class_instance, "one").to_return_value(
"one"
)
with self.assertRaises(TypeError):
# TypeError: type of return must be int; got str instead
with self.assertRaises(testslide.lib.TypeCheckError):
# TypeCheckError: type of return must be int; got str instead
some_class_instance.one()
Limitations
Expand Down
6 changes: 3 additions & 3 deletions docs/patching/mock_constructor/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Type Validation
.. code-block:: python
import sys
import testslide
import testslide, testslide.lib
class Messenger:
def __init__(self, message: str):
Expand All @@ -74,8 +74,8 @@ Type Validation
def test_argument_type_validation(self):
messenger_mock = testslide.StrictMock(template=Messenger)
self.mock_constructor(sys.modules[__name__], "Messenger").to_return_value(messenger_mock)
with self.assertRaises(TypeError):
# TypeError: Call with incompatible argument types:
with self.assertRaises(testslide.lib.TypeCheckError):
# TypeCheckError: Call with incompatible argument types:
# 'message': type of message must be str; got int instead
Messenger(message=1)
Expand Down
8 changes: 4 additions & 4 deletions docs/strict_mock/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ When type annotation is available for attributes, ``StrictMock`` won't allow set
In [5]: mock.VERSION = 1.2
(...)
TypeError: type of VERSION must be str; got float instead
TypeCheckError: type of VERSION must be str; got float instead
Method Signature
================
Expand All @@ -192,7 +192,7 @@ Method signatures must match the signature of the equivalent method at the templ
In [5]: mock.is_odd(2, 'invalid')
(...)
TypeError: too many positional arguments
TypeCheckError: too many positional arguments
Method Argument Type
====================
Expand All @@ -217,7 +217,7 @@ Methods with type annotation will have call arguments validated against it and i
In [6]: mock.is_odd("1")
(...)
TypeError: Call with incompatible argument types:
TypeCheckError: Call with incompatible argument types:
'x': type of x must be int; got str instead
Method Return Type
Expand All @@ -238,7 +238,7 @@ Methods with return type annotated will have its return value type validated as
In [4]: mock.is_odd = lambda x: 1
(...)
TypeError: type of return must be bool; got int instead
TypeCheckError: type of return must be bool; got int instead
Setting Methods With Callables
==============================
Expand Down
9 changes: 5 additions & 4 deletions tests/lib_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def assert_passes(self, *args, **kwargs):
@context.function
def assert_fails(self, *args, **kwargs):
with self.assertRaisesRegex(
TypeError,
testslide.lib.TypeCheckError,
"Call to "
+ self.callable_template.__name__
+ " has incompatible argument types",
Expand Down Expand Up @@ -87,7 +87,7 @@ def fails_for_args_as_kwargs_with_invalid_types(self):
@context.example
def gives_correct_error_message_for_invalid_types(self):
with self.assertRaises(
TypeError,
testslide.lib.TypeCheckError,
msg=(
"Call to test_function has incompatible argument types:\n"
" 'arg1': type of arg1 must be str; got int instead\n"
Expand Down Expand Up @@ -298,7 +298,7 @@ def assert_fails(self, value):
assert_regex = (
r"(?s)type of return must be .+; got .+ instead: .+Defined at .+:\d+"
)
with self.assertRaisesRegex(TypeError, assert_regex):
with self.assertRaisesRegex(testslide.lib.TypeCheckError, assert_regex):
testslide.lib._validate_return_type(
self.callable_template, value, self.caller_frame_info
)
Expand Down Expand Up @@ -350,7 +350,8 @@ def passes_for_valid_forward_reference(self):
@context.example
def fails_for_valid_forward_reference_but_bad_type_passed(self):
with self.assertRaisesRegex(
TypeError, "type of return must be one of .*; got int instead:"
testslide.lib.TypeCheckError,
"type of return must be one of .*; got int instead:",
):
testslide.lib._validate_return_type(
Foo.get_maybe_foo, 33, self.caller_frame_info
Expand Down
5 changes: 3 additions & 2 deletions tests/mock_async_callable_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import contextlib
from testslide.strict_mock import StrictMock
from . import sample_module
from testslide.lib import TypeCheckError


@context("mock_async_callable()")
Expand Down Expand Up @@ -122,8 +123,8 @@ async def value(self):
return 1

@context.example
async def raises_TypeError(self):
with self.assertRaises(TypeError):
async def raises_TypeCheckError(self):
with self.assertRaises(TypeCheckError):
await self.callable_target(
*self.call_args, **self.call_kwargs
)
Expand Down
9 changes: 5 additions & 4 deletions tests/mock_callable_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from testslide.strict_mock import StrictMock
import os
from . import sample_module
from testslide.lib import TypeCheckError


@context("mock_callable()")
Expand Down Expand Up @@ -156,12 +157,12 @@ def type_validation(context):
@context.sub_context
def arguments(context):
@context.example
def raises_TypeError_for_invalid_types(self):
def raises_TypeCheckError_for_invalid_types(self):
bad_signature_args = (1234 for arg in self.call_args)
bad_signature_kargs = {
k: 1234 for k, v in self.call_kwargs.items()
}
with self.assertRaises(TypeError):
with self.assertRaises(TypeCheckError):
self.callable_target(
*bad_signature_args, **bad_signature_kargs
)
Expand Down Expand Up @@ -193,8 +194,8 @@ def with_invalid_return_type(context):
context.memoize("value", lambda self: 1)

@context.example
def raises_TypeError(self):
with self.assertRaises(TypeError):
def raises_TypeCheckError(self):
with self.assertRaises(TypeCheckError):
self.callable_target(
*self.call_args, **self.call_kwargs
)
Expand Down
3 changes: 2 additions & 1 deletion tests/mock_constructor_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from testslide.mock_callable import _MockCallableDSL
from testslide.strict_mock import StrictMock
from typing import Optional
from testslide.lib import TypeCheckError


class _PrivateClass(object):
Expand Down Expand Up @@ -520,7 +521,7 @@ def it_passes_with_valid_types(self):

@context.example
def it_fails_with_invalid_types(self):
with self.assertRaises(TypeError):
with self.assertRaises(TypeCheckError):
self.target(message=1234)

@context.sub_context("with type_validation=False")
Expand Down
19 changes: 10 additions & 9 deletions tests/strict_mock_testslide.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import os

from testslide.dsl import context, xcontext, fcontext, Skip # noqa: F401
from testslide.lib import TypeCheckError


def extra_arg_with_wraps(f):
Expand Down Expand Up @@ -441,19 +442,19 @@ def allows_setting_valid_type_with_generic_mock(self):
self.strict_mock.non_callable = StrictMock()

@context.example
def raises_TypeError_when_setting_invalid_type(self):
with self.assertRaises(TypeError):
def raises_TypeCheckError_when_setting_invalid_type(self):
with self.assertRaises(TypeCheckError):
self.strict_mock.non_callable = 1

@context.example
def raises_TypeError_when_setting_with_mock_with_invalid_type_template(
def raises_TypeCheckError_when_setting_with_mock_with_invalid_type_template(
self,
):
with self.assertRaises(TypeError):
with self.assertRaises(TypeCheckError):
self.strict_mock.non_callable = unittest.mock.Mock(
spec=int
)
with self.assertRaises(TypeError):
with self.assertRaises(TypeCheckError):
self.strict_mock.non_callable = StrictMock(template=int)

@context.sub_context("with type_validation=False")
Expand Down Expand Up @@ -564,7 +565,7 @@ def fails_on_invalid_argument_type_call(self):
self.test_method_name,
lambda message: None,
)
with self.assertRaises(TypeError):
with self.assertRaises(TypeCheckError):
getattr(
self.strict_mock,
self.test_method_name,
Expand All @@ -577,7 +578,7 @@ def fails_on_invalid_return_type(self):
self.test_method_name,
lambda message: 1234,
)
with self.assertRaises(TypeError):
with self.assertRaises(TypeCheckError):
getattr(
self.strict_mock,
self.test_method_name,
Expand Down Expand Up @@ -972,7 +973,7 @@ async def mock(msg):
return "mock "

setattr(self.strict_mock, self.method_name, mock)
with self.assertRaises(TypeError):
with self.assertRaises(TypeCheckError):
await getattr(
self.strict_mock, self.method_name
)(1)
Expand All @@ -985,7 +986,7 @@ async def mock(message):
setattr(
self.strict_mock, self.method_name, mock,
)
with self.assertRaises(TypeError):
with self.assertRaises(TypeCheckError):
await getattr(
self.strict_mock, self.method_name,
)("message")
Expand Down
27 changes: 20 additions & 7 deletions testslide/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@
##


class TypeCheckError(BaseException):
"""
Raised when bad typing is detected during runtime. It inherits from
BaseException to prevent the exception being caught and hidden by the code
being tested, letting it surface to the test runner.
"""

pass


class WrappedMock(unittest.mock.NonCallableMock):
"""Needed to be able to show the useful qualified name for mock specs"""

Expand Down Expand Up @@ -150,7 +160,10 @@ def wrapped_check_type(
), unittest.mock.patch.object(
typeguard, "qualified_name", new=wrapped_qualified_name
):
typeguard.check_type(name, value, expected_type)
try:
typeguard.check_type(name, value, expected_type)
except TypeError as type_error:
raise TypeCheckError(str(type_error))


def _validate_callable_arg_types(
Expand All @@ -175,7 +188,7 @@ def _validate_callable_arg_types(
continue

_validate_argument_type(expected_type, argname, args[idx])
except TypeError as type_error:
except TypeCheckError as type_error:
type_errors.append(f"{repr(argname)}: {type_error}")

for argname, value in kwargs.items():
Expand All @@ -185,11 +198,11 @@ def _validate_callable_arg_types(
continue

_validate_argument_type(expected_type, argname, value)
except TypeError as type_error:
except TypeCheckError as type_error:
type_errors.append(f"{repr(argname)}: {type_error}")

if type_errors:
raise TypeError(
raise TypeCheckError(
"Call to "
+ callable_template.__name__
+ " has incompatible argument types:\n "
Expand Down Expand Up @@ -257,9 +270,9 @@ def _validate_return_type(template, value, caller_frame_info):
if expected_type:
try:
_validate_argument_type(expected_type, "return", value)
except TypeError as type_error:
raise TypeError(
f"{str(type_error)}: {repr(value)}\n"
except TypeCheckError as runtime_type_error:
raise TypeCheckError(
f"{str(runtime_type_error)}: {repr(value)}\n"
f"Defined at {caller_frame_info.filename}:"
f"{caller_frame_info.lineno}"
)
Expand Down

0 comments on commit 252bd92

Please sign in to comment.