Skip to content

Commit ddcd8e6

Browse files
committed
Migrate all tests from aresponses to KMock
Signed-off-by: Sergey Vasilyev <nolar@nolar.info>
1 parent f9d6c38 commit ddcd8e6

25 files changed

+505
-1026
lines changed

docs/testing.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,30 @@ exit code and output are available to the test (for additional assertions).
4040
.. note::
4141
The operator runs against the cluster which is currently authenticated ---
4242
same as if would be executed with `kopf run`.
43+
44+
45+
Mock server
46+
===========
47+
48+
KMock is a supplimentary project to run a local mock server for any HTTP API, and for Kubernetes API in particular — with extended supported of Kubernetes API endpoints, resource discovery, and implicit in-memory object persistence.
49+
50+
Use KMock when you need to run a very lightweight simulation of the Kubernetes API without deploying the heavy Kubernetes cluster nearby, for example when migrating to/from Kopf.
51+
52+
53+
.. code-block:: python
54+
55+
import kmock
56+
import requests
57+
58+
def test_object_patching(kmock: kmock.KubernetesEmulator) -> None:
59+
kmock.objects['kopf.dev/v1/kopfexamples', 'ns1', 'name1'] = {'spec': 123}
60+
requests.patch(str(kmock.url) + '/kopf.dev/v1/namespaces/ns1/name1', json={'spec': 456})
61+
assert len(kmock.requests) == 1
62+
assert kmock.requests[0].method == 'patch'
63+
assert kmock.objects['kopf.dev/v1/kopfexamples', 'ns1', 'name1'] == {'spec': 456}
64+
65+
KMock's detailed documentation is out of scope of Kopf's documentation. The project and its documentation can be found at:
66+
67+
* https://kmock.readthedocs.io/
68+
* https://github.com/nolar/kmock
69+
* https://pypi.org/project/kmock/

kopf/_cogs/aiokits/aiotasks.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,13 @@ async def spawn(
344344
await self._pending_coros.put(SchedulerJob(coro=coro, name=name))
345345
self._condition.notify_all() # -> task_spawner()
346346

347+
# Give the spawner some asyncio cycles to actually spawn and maybe end the task instantly.
348+
# This barely ever happens with real worker(); it is mainly for tests in `test_queueing.py`:
349+
# Depending on luck, they were arriving to `_wait_for_depletion()` with their mocked workers
350+
# either "done", or "pending", thus giving looptime==0 or looptime==exit_timeout (randomly).
351+
# With this extra sleep, such mocked workers are now "done" and the looptime==0.
352+
await asyncio.sleep(0)
353+
347354
def _can_spawn(self) -> bool:
348355
return (not self._pending_coros.empty() and
349356
(self._limit is None or len(self._running_tasks) < self._limit))

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ docs = [
7070
"furo",
7171
]
7272
test = [
73-
"aresponses",
7473
"astpath[xpath]",
7574
"certbuilder",
7675
"certvalidator",
7776
"codecov",
7877
"coverage>=7.12.0",
7978
"freezegun",
79+
"kmock>=0.3",
8080
"looptime>=0.7",
8181
"lxml",
8282
"pyngrok",

tests/admission/test_webhook_detection.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# so we mock all external(!) libraries to return the results as we expect them.
88
# This reduces the quality of the tests, but makes them simple.
99
@pytest.fixture(autouse=True)
10-
def pathmock(mocker, fake_vault, enforced_context, aresponses, hostname):
10+
def pathmock(mocker, fake_vault):
1111
mocker.patch('ssl.get_server_certificate', return_value='')
1212
mocker.patch('certvalidator.ValidationContext')
1313
validator = mocker.patch('certvalidator.CertificateValidator')
@@ -17,14 +17,14 @@ def pathmock(mocker, fake_vault, enforced_context, aresponses, hostname):
1717
return pathmock
1818

1919

20-
async def test_no_detection(hostname, aresponses):
21-
aresponses.add(hostname, '/version', 'get', {'gitVersion': 'v1.2.3'})
20+
async def test_no_detection(kmock):
21+
kmock['get /version'] << {'gitVersion': 'v1.2.3'}
2222
hostname = await ClusterDetector().guess_host()
2323
assert hostname is None
2424

2525

26-
async def test_dependencies(hostname, aresponses, no_certvalidator):
27-
aresponses.add(hostname, '/version', 'get', {'gitVersion': 'v1.2.3'})
26+
async def test_dependencies(kmock, no_certvalidator):
27+
kmock['get /version'] << {'gitVersion': 'v1.2.3'}
2828
with pytest.raises(ImportError) as err:
2929
await ClusterDetector().guess_host()
3030
assert "pip install certvalidator" in str(err.value)
@@ -60,46 +60,41 @@ async def test_k3d_via_subject_org(pathmock):
6060
assert hostname == 'host.k3d.internal'
6161

6262

63-
async def test_k3d_via_version_infix(hostname, aresponses):
64-
aresponses.add(hostname, '/version', 'get', {'gitVersion': 'v1.20.4+k3s1'})
63+
async def test_k3d_via_version_infix(kmock):
64+
kmock['get /version'] << {'gitVersion': 'v1.20.4+k3s1'}
6565
hostname = await ClusterDetector().guess_host()
6666
assert hostname == 'host.k3d.internal'
6767

6868

69-
async def test_server_detects(responder, aresponses, hostname, caplog, assert_logs):
70-
caplog.set_level(0)
71-
aresponses.add(hostname, '/version', 'get', {'gitVersion': 'v1.20.4+k3s1'})
69+
async def test_server_detects(kmock, responder, assert_logs):
70+
kmock['get /version'] << {'gitVersion': 'v1.20.4+k3s1'}
7271
server = WebhookAutoServer(insecure=True)
7372
async with server:
7473
async for _ in server(responder.fn):
7574
break # do not sleep
7675
assert_logs(["Cluster detection found the hostname: host.k3d.internal"])
7776

7877

79-
async def test_server_works(
80-
responder, aresponses, hostname, caplog, assert_logs):
81-
caplog.set_level(0)
82-
aresponses.add(hostname, '/version', 'get', {'gitVersion': 'v1.20.4'})
78+
async def test_server_works(kmock, responder, assert_logs):
79+
kmock['get /version'] << {'gitVersion': 'v1.20.4'}
8380
server = WebhookAutoServer(insecure=True)
8481
async with server:
8582
async for _ in server(responder.fn):
8683
break # do not sleep
8784
assert_logs(["Cluster detection failed, running a simple local server"])
8885

8986

90-
async def test_tunnel_detects(responder, pyngrok_mock, aresponses, hostname, caplog, assert_logs):
91-
caplog.set_level(0)
92-
aresponses.add(hostname, '/version', 'get', {'gitVersion': 'v1.20.4+k3s1'})
87+
async def test_tunnel_detects(kmock, responder, pyngrok_mock, assert_logs):
88+
kmock['get /version'] << {'gitVersion': 'v1.20.4+k3s1'}
9389
server = WebhookAutoTunnel()
9490
async with server:
9591
async for _ in server(responder.fn):
9692
break # do not sleep
9793
assert_logs(["Cluster detection found the hostname: host.k3d.internal"])
9894

9995

100-
async def test_tunnel_works(responder, pyngrok_mock, aresponses, hostname, caplog, assert_logs):
101-
caplog.set_level(0)
102-
aresponses.add(hostname, '/version', 'get', {'gitVersion': 'v1.20.4'})
96+
async def test_tunnel_works(kmock, responder, pyngrok_mock, assert_logs):
97+
kmock['get /version'] << {'gitVersion': 'v1.20.4'}
10398
server = WebhookAutoTunnel()
10499
async with server:
105100
async for _ in server(responder.fn):

0 commit comments

Comments
 (0)