"""Generic test cases."""
import unittest
import warnings
from collections.abc import Collection, Iterable, Mapping, MutableMapping
from textwrap import dedent
from typing import (
Any,
ClassVar,
Generic,
TypeVar,
)
__all__ = [
"GenericTestCase",
"MetaTestCase",
"TestsTestCase",
]
T = TypeVar("T")
X = TypeVar("X")
[docs]
class GenericTestCase(Generic[T], unittest.TestCase):
"""Generic tests."""
cls: ClassVar[type[T]]
kwargs: ClassVar[Mapping[str, Any] | None] = None
instance: T
[docs]
def setUp(self) -> None:
"""Set up the generic testing method."""
if not hasattr(self, "cls"):
self.skipTest(
dedent(
f"""\
The class variable `cls` was not set on {self.__class__}.
If you have implemented a subclass of :class:`unittest_template.GenericTestCase`,
make sure you do it by only importing :mod:`unittest_template`, then accessing it
with the dot operator. Do NOT do ``from unittest_template import GenericTestCase``,
otherwise your testing harness might collect it as a stand-alone test and try to
run it, which will always result in this failure.
"""
)
)
self.pre_setup_hook()
kwargs = self.kwargs or {}
self.instance_kwargs = self._pre_instantiation_hook(kwargs=dict(kwargs))
self.instance = self.cls(**self.instance_kwargs)
self.post_instantiation_hook()
[docs]
def pre_setup_hook(self) -> None:
"""Run before setUp."""
def _pre_instantiation_hook(self, kwargs: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
"""Perform actions before instantiation, potentially modyfing kwargs."""
return kwargs
[docs]
def post_instantiation_hook(self) -> None:
"""Perform actions after instantiation."""
[docs]
def test_instance(self) -> None:
"""Trivially check the instance matches the class."""
self.assertIsInstance(self.instance, self.cls)
def get_subclasses(cls: type[X]) -> Iterable[type[X]]:
"""Get all subclasses.
:param cls: The ancestor class
:yields: Descendant classes of the ancestor class
"""
for subclass in cls.__subclasses__():
yield from get_subclasses(subclass)
yield subclass
[docs]
class TestsTestCase(MetaTestCase[T], Generic[T]):
"""A backwards compatible wrapper of MetaTestCase."""
[docs]
def setUp(self) -> None:
"""Set up the test case."""
warnings.warn(
"unittest_templates.TestsTestCase has been renamed to unittest_tempaltes.MetaTestCase",
DeprecationWarning,
stacklevel=2,
)
super().setUp()