When asking a question, it is very convenient to offer a Minimal Reproducible Example. So remove the unnecessary fastapi and starlette, and provide the code for the test you are trying to write.
Here it is :
# file: so74695297_main.py
from so74695297_log import Logger
from so74695297_routes import my_route
log = Logger(name="logger-1")
log.write("logger started")
def main(): # FAKE IMPL
log.write(message=f"in main()")
my_route()
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO) # for demo
main()
# file: so74695297_routes.py
from so74695297_log import Logger
log = Logger(name="logger-1")
def my_route(): # FAKE IMPL
log.write(message=f"route")
# file: so74695297_log.py
import logging
class Logger:
def __init__(self, name):
self._logger = logging.getLogger(name) # FAKE IMPL
def write(self, message):
self._logger.info(message) # FAKE IMPL
when ran (the main.py
file does something) :
INFO:logger-1:in main()
INFO:logger-1:route
Which is the expected output when the loggers don't fave any formatter.
Then adding a test :
# file: so74695297_test.py
import unittest
import unittest.mock as mock
from so74695297_routes import my_route
class TestMyRoute(unittest.TestCase):
def test__my_route_write_a_log(self):
spy_logger = mock.Mock()
with mock.patch("so74695297_log.Logger", new=spy_logger):
my_route()
assert spy_logger.assert_called()
if __name__ == "__main__":
unittest.main() # for demo
Ran 1 test in 0.010s
FAILED (failures=1)
Failure
Traceback (most recent call last):
File "/home/stack_overflow/so74695297_test.py", line 12, in test__my_route_write_a_log
assert spy_logger.assert_called()
File "/usr/lib/python3.8/unittest/mock.py", line 882, in assert_called
raise AssertionError(msg)
AssertionError: Expected 'mock' to have been called.
Now we have something to work with !
As @MrBeanBremen indicated, the fact that your logger is configured at import time (even when not being the "main" module) complicates things.
The problem is that, by the time the mock.patch
line runs, the modules have already been imported and created their Logger. What we could do instead is mock the Logger.write
method :
def test__my_route_writes_a_log(self):
with mock.patch("so74695297_log.Logger.write") as spy__Logger_write:
my_route()
spy__Logger_write.assert_called_once_with(message="route")
Ran 1 test in 0.001s
OK
If you prefer using the decorator form :
@mock.patch("so74695297_log.Logger.write")
def test__my_route_writes_a_log(self, spy__Logger_write):
my_route()
spy__Logger_write.assert_called_once_with(message="route")
Because we mocked the class's method, each Logger instance has a mock version of write
:
# vvvv
@mock.patch("so74695297_main.Logger.write")
def test__main_writes_a_log(self, spy__Logger_write):
main()
# assert False, spy__Logger_write.mock_calls
spy__Logger_write.assert_any_call(message="in main()")
In the end, main.Logger.write
is essentially the same thing as routes.Logger.write
and as log.Logger.write
, just a reference to the same "method" object. Mock from one way, mock for all the others too.