Skip to content

Commit

Permalink
Merge branch 'kwargs-decorator'
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxcode123 committed Oct 11, 2024
2 parents 13ac465 + 5c46f8f commit 71fe379
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 66 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "unittest-extensions"
version = "0.2.5"
version = "0.3.0"
authors = [
{ name="Maximos Nikiforakis", email="[email protected]" },
]
Expand Down
66 changes: 45 additions & 21 deletions src/unittest_extensions/case.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from unittest import TestCase as BaseTestCase
from typing import Any, Dict
from typing import Any, Dict, Tuple
from abc import abstractmethod
from warnings import warn
from copy import deepcopy
Expand All @@ -12,51 +12,69 @@ class TestCase(BaseTestCase):
Extends unittest.TestCase with methods that assert the result of a defined
`subject` method.
```
from unittest_extensions import TestCase, args
Inherit from this class for your test-case classes and decorate test methods
with the @args decorator.
Examples:
>>> from unittest_extensions import TestCase, args
class MyClass:
def my_method(self, a, b):
return a + b
>>> class MyClass:
... def my_method(self, a, b):
... return a + b
>>> class TestMyMethod(TestCase):
... def subject(self, a, b):
... return MyClass().my_method(a, b)
class TestMyMethod(TestCase):
def subject(self, a, b):
return MyClass().my_method(a, b)
... @args(None, 2)
... def test_none_plus_int(self):
... self.assertResultRaises(TypeError)
@args({"a": None, "b": 2})
def test_none_plus_int(self):
self.assertResultRaises(TypeError)
... @args(a=10, b=22.1)
... def test_int_plus_float(self):
... self.assertResult(32.1)
@args({"a": 10, "b": 22.1})
def test_int_plus_float(self):
self.assertResult(32.1)
```
... @args("1", b="2")
... def test_str_plus_str(self):
... self.assertResult("12")
"""

@abstractmethod
def subject(self, **kwargs) -> Any: ...
def subject(self, *args, **kwargs) -> Any:
raise TestError("No 'subject' method found; perhaps you mispelled it?")

def subjectKwargs(self) -> Dict[str, Any]:
"""
Return the keyword arguments of the subject.
The dictionary returned is a copy of the original arguments. Thus,
The dictionary returned is a deep copy of the original arguments. Thus,
the arguments that the subject receives cannot be mutated by mutating
the returned object of this method.
"""
# NOTE: deepcopy keeps a reference of the copied object. This can cause
# issues with memory.
return deepcopy(self._subjectKwargs)

def subjectArgs(self) -> Tuple:
"""
Return the positional arguments of the subject.
The tuple returned is a deep copy of the original arguments. Thus,
the arguments that the subject receives cannot be mutated by mutating
the returned object of this method.
"""
# NOTE: deepcopy keeps a reference of the copied object. This can cause
# issues with memory.
return deepcopy(self._subjectArgs)

def result(self) -> Any:
"""
Result of the `subject` called with arguments defined by the `args`
decorator.
Result of the `subject` called with arguments defined by the `args` decorator.
"""
try:
self._subjectResult = self.subject(**self._subjectKwargs)
self._subjectResult = self.subject(
*self._subjectArgs, **self._subjectKwargs
)
return self._subjectResult
except Exception as e:
if len(e.args) == 0:
Expand Down Expand Up @@ -328,6 +346,11 @@ def assertResultDict(self, dct):
self.assertDictEqual(self.result(), dct)

def _callTestMethod(self, method):
if hasattr(method, "_subjectArgs"):
self._subjectArgs = method._subjectArgs
else:
self._subjectArgs = tuple()

if hasattr(method, "_subjectKwargs"):
self._subjectKwargs = method._subjectKwargs
else:
Expand All @@ -341,3 +364,4 @@ def _callTestMethod(self, method):
stacklevel=3,
)
self._subjectKwargs = {}
self._subjectArgs = tuple()
45 changes: 39 additions & 6 deletions src/unittest_extensions/decorator.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
def args(kwargs):
import functools


def args(*args, **kwargs):
"""
Decorate test methods to define arguments for your subject.
Decorate test methods to define positional and/or keyword arguments for your
`subject` method.
Examples:
>>> from unittest_extensions import TestCase, args
>>> class MyClass:
... def my_method(self, a, b):
... return a + b
>>> class TestMyMethod(TestCase):
... def subject(self, a, b):
... return MyClass().my_method(a, b)
... @args(None, 2)
... def test_none_plus_int(self):
... self.assertResultRaises(TypeError)
... @args(a=10, b=22.1)
... def test_int_plus_float(self):
... self.assertResult(32.1)
... @args("1", b="2")
... def test_str_plus_str(self):
... self.assertResult("12")
"""

def wrapper(method):
method._subjectKwargs = kwargs
return method
def args_decorator(test_method):
test_method._subjectArgs = args
test_method._subjectKwargs = kwargs

@functools.wraps(test_method)
def wrapped_test_method(*_args, **_kwargs):
return test_method(*_args, **_kwargs)

return wrapped_test_method

return wrapper
return args_decorator
Loading

0 comments on commit 71fe379

Please sign in to comment.