Skip to content

Commit 6c7dd34

Browse files
fix: Eliminate RuntimeWarning about unawaited coroutines in test suite (issue #22) (#42)
* fix: Eliminate RuntimeWarning about unawaited coroutines in test suite (issue #22) This commit completely eliminates RuntimeWarning messages about unawaited coroutines that were cluttering the test output. Changes: - Added autouse fixture `mock_cache_sync_manager` that properly mocks cache_sync_manager for all tests in the module - Created real async function for sync_cache_with_config to avoid AsyncMock introspection issues - Fixed all tests that patch asyncio.run to properly handle coroutines by either running them with real asyncio.run or closing them before raising exceptions - Removed redundant cache_sync_manager patches from individual tests since the autouse fixture handles it globally - Updated TestStatusCommand tests to use the fixture parameter instead of creating new patches All 22 tests pass successfully with ZERO RuntimeWarnings. [AI-assisted] * Fixed formatting Signed-off-by: florath-ai-assistant[bot] <Andreas.Florath@telekom.de> --------- Signed-off-by: florath-ai-assistant[bot] <Andreas.Florath@telekom.de> Co-authored-by: florath-ai-assistant[bot] <Andreas.Florath@telekom.de>
1 parent 09d9890 commit 6c7dd34

File tree

1 file changed

+96
-43
lines changed

1 file changed

+96
-43
lines changed

tests/unit/test_cli.py

Lines changed: 96 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# SPDX-License-Identifier: MIT
22
"""Tests for the CLI module."""
33

4+
import asyncio
45
import json
56
import tempfile
67
from pathlib import Path
7-
from unittest.mock import AsyncMock, Mock, patch
8+
from unittest.mock import AsyncMock, MagicMock, Mock, patch
89

910
import pytest
1011
from click.testing import CliRunner
@@ -24,6 +25,32 @@ def runner():
2425
return CliRunner()
2526

2627

28+
@pytest.fixture(autouse=True)
29+
def mock_cache_sync_manager():
30+
"""Mock cache_sync_manager to prevent unawaited coroutine warnings.
31+
32+
This fixture is autouse=True so it applies to all tests in this module,
33+
preventing the real cache_sync_manager from being accessed and creating
34+
unawaited coroutines during test execution.
35+
"""
36+
37+
# Create a real async function that can be awaited
38+
async def _mock_sync_impl(*args, **kwargs):
39+
"""Mock implementation of sync_cache_with_config."""
40+
return {}
41+
42+
# Create a mock manager with all required methods
43+
mock_manager = MagicMock()
44+
# Assign the actual async function (not AsyncMock) to avoid introspection issues
45+
mock_manager.sync_cache_with_config = _mock_sync_impl
46+
mock_manager.get_sync_status = MagicMock(
47+
return_value={"sync_in_progress": False, "backends": {}}
48+
)
49+
50+
with patch("aletheia_probe.cli.cache_sync_manager", mock_manager):
51+
yield mock_manager
52+
53+
2754
@pytest.fixture
2855
def mock_assessment_result():
2956
"""Create mock assessment result."""
@@ -54,8 +81,15 @@ class TestAssessCommand:
5481

5582
def test_assess_basic_usage(self, runner, mock_assessment_result):
5683
"""Test basic journal command usage."""
84+
# Store the real asyncio.run before patching
85+
real_asyncio_run = asyncio.run
86+
87+
def run_coro(coro):
88+
"""Run the coroutine using real asyncio.run."""
89+
return real_asyncio_run(coro)
90+
5791
with (
58-
patch("aletheia_probe.cli.asyncio.run") as mock_run,
92+
patch("aletheia_probe.cli.asyncio.run", side_effect=run_coro) as mock_run,
5993
patch("aletheia_probe.cli._async_assess_publication") as mock_async_assess,
6094
):
6195
mock_async_assess.return_value = None
@@ -67,10 +101,19 @@ def test_assess_basic_usage(self, runner, mock_assessment_result):
67101

68102
def test_assess_with_verbose_flag(self, runner):
69103
"""Test journal command with verbose flag."""
104+
# Store the real asyncio.run before patching
105+
real_asyncio_run = asyncio.run
106+
107+
def run_coro(coro):
108+
"""Run the coroutine using real asyncio.run."""
109+
return real_asyncio_run(coro)
110+
70111
with (
71-
patch("aletheia_probe.cli.asyncio.run") as mock_run,
112+
patch("aletheia_probe.cli.asyncio.run", side_effect=run_coro) as mock_run,
72113
patch("aletheia_probe.cli._async_assess_publication") as mock_async_assess,
73114
):
115+
mock_async_assess.return_value = None
116+
74117
result = runner.invoke(main, ["journal", "Test Journal", "--verbose"])
75118

76119
assert result.exit_code == 0
@@ -79,10 +122,19 @@ def test_assess_with_verbose_flag(self, runner):
79122

80123
def test_assess_with_json_format(self, runner):
81124
"""Test journal command with JSON output format."""
125+
# Store the real asyncio.run before patching
126+
real_asyncio_run = asyncio.run
127+
128+
def run_coro(coro):
129+
"""Run the coroutine using real asyncio.run."""
130+
return real_asyncio_run(coro)
131+
82132
with (
83-
patch("aletheia_probe.cli.asyncio.run") as mock_run,
133+
patch("aletheia_probe.cli.asyncio.run", side_effect=run_coro) as mock_run,
84134
patch("aletheia_probe.cli._async_assess_publication") as mock_async_assess,
85135
):
136+
mock_async_assess.return_value = None
137+
86138
result = runner.invoke(
87139
main, ["journal", "Test Journal", "--format", "json"]
88140
)
@@ -137,12 +189,12 @@ def test_sync_command_success(self, runner):
137189
"backend2": {"status": "current"},
138190
}
139191

140-
with (
141-
patch("aletheia_probe.cli.asyncio.run") as mock_run,
142-
patch("aletheia_probe.cli.cache_sync_manager") as mock_cache_sync,
143-
):
144-
mock_run.return_value = mock_sync_result
192+
def run_coro(coro):
193+
"""Run coroutine and return mock result."""
194+
coro.close() # Close without running
195+
return mock_sync_result
145196

197+
with patch("aletheia_probe.cli.asyncio.run", side_effect=run_coro) as mock_run:
146198
result = runner.invoke(main, ["sync"])
147199

148200
assert result.exit_code == 0
@@ -151,12 +203,13 @@ def test_sync_command_success(self, runner):
151203

152204
def test_sync_command_with_force(self, runner):
153205
"""Test sync command with force flag."""
154-
with (
155-
patch("aletheia_probe.cli.asyncio.run") as mock_run,
156-
patch("aletheia_probe.cli.cache_sync_manager") as mock_cache_sync,
157-
):
158-
mock_run.return_value = {}
159206

207+
def run_coro(coro):
208+
"""Run coroutine and return empty dict."""
209+
coro.close() # Close without running
210+
return {}
211+
212+
with patch("aletheia_probe.cli.asyncio.run", side_effect=run_coro) as mock_run:
160213
result = runner.invoke(main, ["sync", "--force"])
161214

162215
assert result.exit_code == 0
@@ -167,9 +220,12 @@ def test_sync_command_skipped(self, runner):
167220
"""Test sync command when sync is skipped."""
168221
mock_sync_result = {"status": "skipped", "reason": "auto_sync_disabled"}
169222

170-
with patch("aletheia_probe.cli.asyncio.run") as mock_run:
171-
mock_run.return_value = mock_sync_result
223+
def run_coro(coro):
224+
"""Run coroutine and return mock result."""
225+
coro.close() # Close without running
226+
return mock_sync_result
172227

228+
with patch("aletheia_probe.cli.asyncio.run", side_effect=run_coro) as mock_run:
173229
result = runner.invoke(main, ["sync"])
174230

175231
assert result.exit_code == 0
@@ -178,9 +234,13 @@ def test_sync_command_skipped(self, runner):
178234

179235
def test_sync_command_error(self, runner):
180236
"""Test sync command with error."""
181-
with patch("aletheia_probe.cli.asyncio.run") as mock_run:
182-
mock_run.side_effect = Exception("Sync failed")
183237

238+
def mock_run_with_cleanup(coro):
239+
"""Mock asyncio.run that properly closes coroutines before raising."""
240+
coro.close() # Close the coroutine to avoid warning
241+
raise Exception("Sync failed")
242+
243+
with patch("aletheia_probe.cli.asyncio.run", side_effect=mock_run_with_cleanup):
184244
result = runner.invoke(main, ["sync"])
185245

186246
assert result.exit_code == 1
@@ -191,7 +251,7 @@ def test_sync_command_error(self, runner):
191251
class TestStatusCommand:
192252
"""Test cases for the status command."""
193253

194-
def test_status_command_success(self, runner):
254+
def test_status_command_success(self, runner, mock_cache_sync_manager):
195255
"""Test status command successful execution."""
196256
mock_status = {
197257
"sync_in_progress": False,
@@ -206,37 +266,34 @@ def test_status_command_success(self, runner):
206266
},
207267
}
208268

209-
with patch("aletheia_probe.cli.cache_sync_manager") as mock_cache_sync:
210-
mock_cache_sync.get_sync_status.return_value = mock_status
269+
mock_cache_sync_manager.get_sync_status.return_value = mock_status
211270

212-
result = runner.invoke(main, ["status"])
271+
result = runner.invoke(main, ["status"])
213272

214-
assert result.exit_code == 0
215-
assert "backend1" in result.output
216-
assert "enabled" in result.output
217-
assert "disabled" in result.output
273+
assert result.exit_code == 0
274+
assert "backend1" in result.output
275+
assert "enabled" in result.output
276+
assert "disabled" in result.output
218277

219-
def test_status_command_sync_in_progress(self, runner):
278+
def test_status_command_sync_in_progress(self, runner, mock_cache_sync_manager):
220279
"""Test status command when sync is in progress."""
221280
mock_status = {"sync_in_progress": True, "backends": {}}
222281

223-
with patch("aletheia_probe.cli.cache_sync_manager") as mock_cache_sync:
224-
mock_cache_sync.get_sync_status.return_value = mock_status
282+
mock_cache_sync_manager.get_sync_status.return_value = mock_status
225283

226-
result = runner.invoke(main, ["status"])
284+
result = runner.invoke(main, ["status"])
227285

228-
assert result.exit_code == 0
229-
assert "progress" in result.output.lower()
286+
assert result.exit_code == 0
287+
assert "progress" in result.output.lower()
230288

231-
def test_status_command_error(self, runner):
289+
def test_status_command_error(self, runner, mock_cache_sync_manager):
232290
"""Test status command with error."""
233-
with patch("aletheia_probe.cli.cache_sync_manager") as mock_cache_sync:
234-
mock_cache_sync.get_sync_status.side_effect = Exception("Status error")
291+
mock_cache_sync_manager.get_sync_status.side_effect = Exception("Status error")
235292

236-
result = runner.invoke(main, ["status"])
293+
result = runner.invoke(main, ["status"])
237294

238-
assert result.exit_code == 1
239-
assert "Status error" in result.output
295+
assert result.exit_code == 1
296+
assert "Status error" in result.output
240297

241298

242299
class TestAddListCommand:
@@ -249,11 +306,7 @@ def test_add_list_success(self, runner):
249306
temp_file = f.name
250307

251308
try:
252-
with (
253-
patch("aletheia_probe.cli.data_updater") as mock_updater,
254-
patch("aletheia_probe.cli.asyncio.run") as mock_run,
255-
patch("aletheia_probe.cli.cache_sync_manager"),
256-
):
309+
with patch("aletheia_probe.cli.data_updater") as mock_updater:
257310
result = runner.invoke(
258311
main,
259312
[

0 commit comments

Comments
 (0)