Skip to content

Commit f1b6b4d

Browse files
committed
test: generate bound model methods tests
1 parent 131b142 commit f1b6b4d

File tree

1 file changed

+87
-0
lines changed

1 file changed

+87
-0
lines changed

tests/unit/conftest.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
from __future__ import annotations
44

5+
import inspect
56
from collections.abc import Generator
7+
from typing import Callable, ClassVar
68
from unittest import mock
79
from warnings import warn
810

@@ -142,3 +144,88 @@ def hetzner_client() -> Generator[Client]:
142144
patcher.start()
143145
yield client
144146
patcher.stop()
147+
148+
149+
def build_kwargs_mock(func: Callable) -> dict[str, mock.Mock]:
150+
"""
151+
Generate a kwargs dict that may be passed to the provided function for testing purposes.
152+
"""
153+
s = inspect.signature(func)
154+
155+
kwargs = {}
156+
for name, param in s.parameters.items():
157+
if name in ("self",):
158+
continue
159+
160+
if param.kind in (param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY):
161+
kwargs[name] = mock.Mock()
162+
continue
163+
164+
# Ignore **kwargs
165+
if param.kind in (param.VAR_KEYWORD,):
166+
continue
167+
168+
raise NotImplementedError(f"unsupported parameter kind: {param.kind}")
169+
170+
return kwargs
171+
172+
173+
def pytest_generate_tests(metafunc: pytest.Metafunc):
174+
"""
175+
Magic function to generate a test for each bound model method.
176+
"""
177+
if "bound_model_method" in metafunc.fixturenames:
178+
metafunc.parametrize("bound_model_method", metafunc.cls.methods)
179+
180+
181+
class BoundModelTestCase:
182+
methods: ClassVar[list[Callable]]
183+
184+
def test_method_list(self, bound_model):
185+
"""
186+
Ensure the list of bound model methods is up to date.
187+
"""
188+
members_count = 0
189+
members_missing = []
190+
for name, member in inspect.getmembers(
191+
bound_model,
192+
lambda m: inspect.ismethod(m)
193+
and m.__func__ in bound_model.__class__.__dict__.values(),
194+
):
195+
# Actions methods are already tested in TestBoundModelActions.
196+
if name in ("__init__", "get_actions", "get_actions_list"):
197+
continue
198+
199+
if member.__func__ in self.__class__.methods:
200+
members_count += 1
201+
else:
202+
members_missing.append(member.__func__.__qualname__)
203+
204+
assert not members_missing, "untested methods:\n" + ",\n".join(members_missing)
205+
assert members_count == len(self.__class__.methods)
206+
207+
def test_method(
208+
self,
209+
resource_client,
210+
bound_model,
211+
bound_model_method: Callable,
212+
):
213+
# Check if the resource client has a method named after the bound model method.
214+
assert hasattr(resource_client, bound_model_method.__name__)
215+
216+
# Mock the resource client method.
217+
resource_client_method_mock = mock.MagicMock()
218+
setattr(
219+
resource_client,
220+
bound_model_method.__name__,
221+
resource_client_method_mock,
222+
)
223+
224+
kwargs = build_kwargs_mock(bound_model_method)
225+
226+
# Call the bound model method
227+
result = getattr(bound_model, bound_model_method.__name__)(**kwargs)
228+
229+
resource_client_method_mock.assert_called_with(bound_model, **kwargs)
230+
231+
assert result is resource_client_method_mock.return_value

0 commit comments

Comments
 (0)