Skip to content

Commit 7b65853

Browse files
committed
Properly mock logger in app module tests
#3 #4 https://docs.python.org/3/library/unittest.mock.html#where-to-patch - Mock the root logger object directly: The project previously had a `mock_logger` pytest fixture in conftest.py that instantiated a root logger with `logging.getLogger()`. The individual log level attributes were then patched with `mocker.patch.object(logger, "debug")`. This was problematic, because Mypy thought that the logger didn't have the attributes, requiring many `# type: ignore[attr-defined]` comments. Instead of `logging.getLogger()`, the root logger object itself will be directly patched whenever it is used for testing. - Remove `# type: ignore[attr-defined]` Mypy comments: now that the `mock_logger` is a proper `MockerFixture`, pytest and pytest-mock will create the necessary attributes automatically. - Correct module spec syntax in `set_app_module()`: importlib needs the module path without the app instance name at the end. The way this function was written before, importlib was just returning `None`. inboard should be able to find the app module before continuing, or raise an `ImportError` exception if module spec is not created. - Move error messages to error log level instead of debug: makes more sense for errors to be logged as errors - Add `e.__class__.__name__` to log messages when exceptions are raised: helps to have the exception class name
1 parent fcd7021 commit 7b65853

2 files changed

Lines changed: 29 additions & 24 deletions

File tree

inboard/start.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ def set_app_module(logger: Logger = logging.getLogger()) -> str:
5656
"""Set the name of the Python module with the app instance to run."""
5757
try:
5858
app_module = str(os.getenv("APP_MODULE"))
59-
importlib.util.find_spec(app_module)
59+
if not importlib.util.find_spec((module := app_module.split(sep=":")[0])):
60+
raise ImportError(f"Unable to find or import {module}")
6061
logger.debug(f"App module set to {app_module}.")
6162
return app_module
6263
except Exception as e:
63-
message = f"Error when setting app module: {e}."
64-
logger.debug(message)
64+
logger.error(f"Error when setting app module: {e.__class__.__name__} {e}.")
6565
raise
6666

6767

tests/test_start.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -262,93 +262,98 @@ class TestSetAppModule:
262262
"""
263263

264264
def test_set_app_module_asgi(
265-
self, mock_logger: logging.Logger, monkeypatch: pytest.MonkeyPatch
265+
self, mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch
266266
) -> None:
267267
"""Test `start.set_app_module` using module path to base ASGI app."""
268+
mock_logger = mocker.patch.object(start.logging, "root", autospec=True)
268269
monkeypatch.setenv("APP_MODULE", "inboard.app.main_base:app")
269270
start.set_app_module(logger=mock_logger)
270-
mock_logger.debug.assert_called_once_with( # type: ignore[attr-defined]
271+
mock_logger.debug.assert_called_once_with(
271272
"App module set to inboard.app.main_base:app."
272273
)
273274

274275
def test_set_app_module_fastapi(
275-
self, mock_logger: logging.Logger, monkeypatch: pytest.MonkeyPatch
276+
self, mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch
276277
) -> None:
277278
"""Test `start.set_app_module` using module path to FastAPI app."""
279+
mock_logger = mocker.patch.object(start.logging, "root", autospec=True)
278280
monkeypatch.setenv("APP_MODULE", "inboard.app.main_fastapi:app")
279281
start.set_app_module(logger=mock_logger)
280-
mock_logger.debug.assert_called_once_with( # type: ignore[attr-defined]
282+
mock_logger.debug.assert_called_once_with(
281283
"App module set to inboard.app.main_fastapi:app."
282284
)
283285

284286
def test_set_app_module_starlette(
285-
self, mock_logger: logging.Logger, monkeypatch: pytest.MonkeyPatch
287+
self, mocker: MockerFixture, monkeypatch: pytest.MonkeyPatch
286288
) -> None:
287289
"""Test `start.set_app_module` using module path to Starlette app."""
290+
mock_logger = mocker.patch.object(start.logging, "root", autospec=True)
288291
monkeypatch.setenv("APP_MODULE", "inboard.app.main_starlette:app")
289292
start.set_app_module(logger=mock_logger)
290-
mock_logger.debug.assert_called_once_with( # type: ignore[attr-defined]
293+
mock_logger.debug.assert_called_once_with(
291294
"App module set to inboard.app.main_starlette:app."
292295
)
293296

294297
def test_set_app_module_custom_asgi(
295298
self,
296299
app_module_tmp_path: Path,
297-
mock_logger: logging.Logger,
300+
mocker: MockerFixture,
298301
monkeypatch: pytest.MonkeyPatch,
299302
) -> None:
300303
"""Test `start.set_app_module` with custom module path to base ASGI app."""
304+
mock_logger = mocker.patch.object(start.logging, "root", autospec=True)
301305
monkeypatch.syspath_prepend(app_module_tmp_path)
302306
monkeypatch.setenv("APP_MODULE", "tmp_app.main_base:app")
303307
start.set_app_module(logger=mock_logger)
304-
mock_logger.debug.assert_called_once_with( # type: ignore[attr-defined]
308+
mock_logger.debug.assert_called_once_with(
305309
"App module set to tmp_app.main_base:app."
306310
)
307311

308312
def test_set_app_module_custom_fastapi(
309313
self,
310314
app_module_tmp_path: Path,
311-
mock_logger: logging.Logger,
315+
mocker: MockerFixture,
312316
monkeypatch: pytest.MonkeyPatch,
313317
) -> None:
314318
"""Test `start.set_app_module` with custom module path to FastAPI app."""
319+
mock_logger = mocker.patch.object(start.logging, "root", autospec=True)
315320
monkeypatch.syspath_prepend(app_module_tmp_path)
316321
monkeypatch.setenv("APP_MODULE", "tmp_app.main_fastapi:app")
317322
start.set_app_module(logger=mock_logger)
318-
mock_logger.debug.assert_called_once_with( # type: ignore[attr-defined]
323+
mock_logger.debug.assert_called_once_with(
319324
"App module set to tmp_app.main_fastapi:app."
320325
)
321326

322327
def test_set_app_module_custom_starlette(
323328
self,
324329
app_module_tmp_path: Path,
325-
mock_logger: logging.Logger,
330+
mocker: MockerFixture,
326331
monkeypatch: pytest.MonkeyPatch,
327332
) -> None:
328333
"""Test `start.set_app_module` with custom module path to Starlette app."""
334+
mock_logger = mocker.patch.object(start.logging, "root", autospec=True)
329335
monkeypatch.syspath_prepend(app_module_tmp_path)
330336
monkeypatch.setenv("APP_MODULE", "tmp_app.main_starlette:app")
331337
start.set_app_module(logger=mock_logger)
332-
mock_logger.debug.assert_called_once_with( # type: ignore[attr-defined]
338+
mock_logger.debug.assert_called_once_with(
333339
"App module set to tmp_app.main_starlette:app."
334340
)
335341

336342
def test_set_app_module_incorrect(
337343
self,
338344
mocker: MockerFixture,
339-
mock_logger: logging.Logger,
340345
monkeypatch: pytest.MonkeyPatch,
341346
) -> None:
342347
"""Test `start.set_app_module` with incorrect module path."""
343-
with pytest.raises(ModuleNotFoundError):
344-
incorrect_module = "inboard.app.incorrect.main:app"
345-
monkeypatch.setenv("APP_MODULE", incorrect_module)
346-
logger_error_msg = "Error when setting app module:"
347-
incorrect_module_msg = f"No module named {incorrect_module}"
348+
mock_logger = mocker.patch.object(start.logging, "root", autospec=True)
349+
monkeypatch.setenv("APP_MODULE", "inboard.app.incorrect:app")
350+
logger_error_msg = "Error when setting app module"
351+
incorrect_module_msg = "Unable to find or import inboard.app.incorrect"
352+
with pytest.raises(ImportError):
348353
start.set_app_module(logger=mock_logger)
349-
mock_logger.debug.assert_called_once_with( # type: ignore[attr-defined]
350-
f"{logger_error_msg} {incorrect_module_msg}."
351-
)
354+
mock_logger.error.assert_called_once_with(
355+
f"{logger_error_msg}: ImportError {incorrect_module_msg}."
356+
)
352357

353358

354359
class TestRunPreStartScript:

0 commit comments

Comments
 (0)