Skip to content

Commit 71fe379

Browse files
committed
Merge branch 'kwargs-decorator'
2 parents 13ac465 + 5c46f8f commit 71fe379

File tree

4 files changed

+315
-66
lines changed

4 files changed

+315
-66
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ build-backend = "setuptools.build_meta"
1616

1717
[project]
1818
name = "unittest-extensions"
19-
version = "0.2.5"
19+
version = "0.3.0"
2020
authors = [
2121
{ name="Maximos Nikiforakis", email="[email protected]" },
2222
]

src/unittest_extensions/case.py

+45-21
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from unittest import TestCase as BaseTestCase
2-
from typing import Any, Dict
2+
from typing import Any, Dict, Tuple
33
from abc import abstractmethod
44
from warnings import warn
55
from copy import deepcopy
@@ -12,51 +12,69 @@ class TestCase(BaseTestCase):
1212
Extends unittest.TestCase with methods that assert the result of a defined
1313
`subject` method.
1414
15-
```
16-
from unittest_extensions import TestCase, args
15+
Inherit from this class for your test-case classes and decorate test methods
16+
with the @args decorator.
1717
18+
Examples:
19+
>>> from unittest_extensions import TestCase, args
1820
19-
class MyClass:
20-
def my_method(self, a, b):
21-
return a + b
21+
>>> class MyClass:
22+
... def my_method(self, a, b):
23+
... return a + b
2224
25+
>>> class TestMyMethod(TestCase):
26+
... def subject(self, a, b):
27+
... return MyClass().my_method(a, b)
2328
24-
class TestMyMethod(TestCase):
25-
def subject(self, a, b):
26-
return MyClass().my_method(a, b)
29+
... @args(None, 2)
30+
... def test_none_plus_int(self):
31+
... self.assertResultRaises(TypeError)
2732
28-
@args({"a": None, "b": 2})
29-
def test_none_plus_int(self):
30-
self.assertResultRaises(TypeError)
33+
... @args(a=10, b=22.1)
34+
... def test_int_plus_float(self):
35+
... self.assertResult(32.1)
3136
32-
@args({"a": 10, "b": 22.1})
33-
def test_int_plus_float(self):
34-
self.assertResult(32.1)
35-
```
37+
... @args("1", b="2")
38+
... def test_str_plus_str(self):
39+
... self.assertResult("12")
3640
"""
3741

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

4146
def subjectKwargs(self) -> Dict[str, Any]:
4247
"""
4348
Return the keyword arguments of the subject.
4449
45-
The dictionary returned is a copy of the original arguments. Thus,
50+
The dictionary returned is a deep copy of the original arguments. Thus,
4651
the arguments that the subject receives cannot be mutated by mutating
4752
the returned object of this method.
4853
"""
4954
# NOTE: deepcopy keeps a reference of the copied object. This can cause
5055
# issues with memory.
5156
return deepcopy(self._subjectKwargs)
5257

58+
def subjectArgs(self) -> Tuple:
59+
"""
60+
Return the positional arguments of the subject.
61+
62+
The tuple returned is a deep copy of the original arguments. Thus,
63+
the arguments that the subject receives cannot be mutated by mutating
64+
the returned object of this method.
65+
"""
66+
# NOTE: deepcopy keeps a reference of the copied object. This can cause
67+
# issues with memory.
68+
return deepcopy(self._subjectArgs)
69+
5370
def result(self) -> Any:
5471
"""
55-
Result of the `subject` called with arguments defined by the `args`
56-
decorator.
72+
Result of the `subject` called with arguments defined by the `args` decorator.
5773
"""
5874
try:
59-
self._subjectResult = self.subject(**self._subjectKwargs)
75+
self._subjectResult = self.subject(
76+
*self._subjectArgs, **self._subjectKwargs
77+
)
6078
return self._subjectResult
6179
except Exception as e:
6280
if len(e.args) == 0:
@@ -328,6 +346,11 @@ def assertResultDict(self, dct):
328346
self.assertDictEqual(self.result(), dct)
329347

330348
def _callTestMethod(self, method):
349+
if hasattr(method, "_subjectArgs"):
350+
self._subjectArgs = method._subjectArgs
351+
else:
352+
self._subjectArgs = tuple()
353+
331354
if hasattr(method, "_subjectKwargs"):
332355
self._subjectKwargs = method._subjectKwargs
333356
else:
@@ -341,3 +364,4 @@ def _callTestMethod(self, method):
341364
stacklevel=3,
342365
)
343366
self._subjectKwargs = {}
367+
self._subjectArgs = tuple()

src/unittest_extensions/decorator.py

+39-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,43 @@
1-
def args(kwargs):
1+
import functools
2+
3+
4+
def args(*args, **kwargs):
25
"""
3-
Decorate test methods to define arguments for your subject.
6+
Decorate test methods to define positional and/or keyword arguments for your
7+
`subject` method.
8+
9+
Examples:
10+
>>> from unittest_extensions import TestCase, args
11+
12+
>>> class MyClass:
13+
... def my_method(self, a, b):
14+
... return a + b
15+
16+
>>> class TestMyMethod(TestCase):
17+
... def subject(self, a, b):
18+
... return MyClass().my_method(a, b)
19+
20+
... @args(None, 2)
21+
... def test_none_plus_int(self):
22+
... self.assertResultRaises(TypeError)
23+
24+
... @args(a=10, b=22.1)
25+
... def test_int_plus_float(self):
26+
... self.assertResult(32.1)
27+
28+
... @args("1", b="2")
29+
... def test_str_plus_str(self):
30+
... self.assertResult("12")
431
"""
532

6-
def wrapper(method):
7-
method._subjectKwargs = kwargs
8-
return method
33+
def args_decorator(test_method):
34+
test_method._subjectArgs = args
35+
test_method._subjectKwargs = kwargs
36+
37+
@functools.wraps(test_method)
38+
def wrapped_test_method(*_args, **_kwargs):
39+
return test_method(*_args, **_kwargs)
40+
41+
return wrapped_test_method
942

10-
return wrapper
43+
return args_decorator

0 commit comments

Comments
 (0)