I have this code that I want to test:
log = logging.getLogger(__name__)
class A(object):
def __init__(self):
log.debug('Init')
but I cannot figure out how to assert that log.debug was called with 'Init'
I tried patching logger but inspecting it I only found a getLogger mock.
I'm sure its simple, but I just cant figure it!
Thanks in advance for any and all help!
You can use patch.object() on the actual logging object. that lets you verify that you're using the correct logger too:
logger = logging.getLogger('path.to.module.under.test')
with mock.patch.object(logger, 'debug') as mock_debug:
run_code_under_test()
mock_debug.assert_called_once_with('Init')
Alternatively, if you're using Pytest, then it already has a fixture that captures logs for you:
def test_bar(caplog):
with caplog.at_level(logging.DEBUG):
run_code_under_test()
assert "Init" in caplog.text
# or, if you really need to check the log-level
assert caplog.records[-1].message == "Init"
assert caplog.records[-1].levelname == "DEBUG"
More info in the pytest docs on logging
Assuming log is a global variable in a module mymod, you want to mock the actual instance that getLogger returned, which is what invokes debug. Then, you can check if log.debug was called with the correct argument.
with mock.patch('mymod.log') as log_mock:
# test code
log_mock.debug.assert_called_with('Init')
I am late for this question but another of way to achieve it is:
#patch('package_name.module_name.log')
def test_log_in_A(self, mocked_log):
a = A()
mocked_log.debug.assert_called_once_with('Init')
This also works:
from unittest.mock import patch
class Test(TestCase):
def test_logger(self):
with patch('logging.Logger.warning') as mocked_logger:
call_func()
mocked_logger.assert_called_once_with('log')
Here is a complete example
"""
Source to test
"""
import logging
logger = logging.getLogger("abc")
def my_fonction():
logger.warning("Oops")
"""
Testing part
"""
import unittest
from unittest.mock import patch, MagicMock
abc_logger = logging.getLogger("abc")
class TestApp(unittest.TestCase):
#patch.object(abc_logger, "warning", MagicMock())
def test_my_fonction(self):
# When
my_fonction()
# Then
abc_logger.warning.assert_called_once()
Related
I am trying to figure out how to use the #patch.object to mock a __init__ and, log.write() for the Logger class, which is imported but isn't used inside the function of a module. The tuorial such as this, https://www.pythontutorial.net/python-unit-testing/python-patch/
, points to the patching needs to be at the target where it is used, not where it comes from. However, every example used shows the target to be mocked inside another function.
In the use case mentioned below, the logger is imported and used to write the log outside the scope of a function. Is there a way to mock the behavior both in main.py and routers.py?
src/apis/main.py
from utils.log import Logger
from routes import route
log = Logger(name="logger-1")
log.write("logger started")
def main():
log = Logger(name="logger-1")
log.write("inside main")
route()
if __name__ == "__main__":
import logging
logging.basicConfig(level=logging.INFO) # for demo
main()
In src/apis/routers/routes.py
from utils.log import Logger
log = Logger(name="logger-1")
log.write, message=f"Inside route")
def route():
log.write, message=f"Logging done.")
In utils/log/logging.py
import logging
Class Logger:
def __init__(self, name):
# needs to be mocked
def write(self, message):
# needs to be mocked to return None
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.
Does pytest provides functionality like unittest.mock to check if the mock was actually called once(or once with some parameter)?
Sample Source code:
my_package/my_module.py
from com.abc.validation import Validation
class MyModule:
def __init__(self):
pass
def will_call_other_package(self):
val = Validation()
val.do()
def run(self):
self.will_call_other_package()
Sample test code for the above source code:
test_my_module.py
import pytest
from pytest_mock import mocker
from my_package.my_module import MyModule
#pytest.fixture
def mock_will_call_other_package(mocker):
mocker.patch('my_package.my_module.will_call_other_package')
#pytest.mark.usefixtures("mock_will_call_other_package")
class TestMyModule:
def test_run(self):
MyModule().run()
#check `will_call_other_package` method is called.
#Looking for something similar to what unittest.mock provide
#mock_will_call_other_package.called_once
If you want to use a fixture that does the patching, you can move the patching into a fixture:
import pytest
from unittest import mock
from my_package.my_module import MyModule
#pytest.fixture
def mock_will_call_other_package():
with mock.patch('my_package.my_module.will_call_other_package') as mocked:
yield mocked
# the mocking will be reverted here, e.g. after the test
class TestMyModule:
def test_run(self, mock_will_call_other_package):
MyModule().run()
mock_will_call_other_package.assert_called_once()
Note that you have to use the fixture parameter in the test. Just using #pytest.mark.usefixtures will not give you access to the mock itself. You can still use it to be effective in all tests in the class, if you don't need to access the mock in all tests (or use autouse=True in the fixture).
Also note that you don't need pytest-mock here - but as mentioned by #hoefling, using it makes the fixture better readable, because you don't need the with clause :
#pytest.fixture
def mock_will_call_other_package(mocker):
yield mocker.patch('my_package.my_module.will_call_other_package')
As an aside: you don't need to import mocker. Fixtures are looked up by name, and available automatically if the respective plugin is installed.
You could try this:
import pytest
from my_package.my_module import MyModule
def test_run(mocker):
mocker.patch('my_package.my_module.will_call_other_package')
MyModule().run()
mock_will_call_other_package.assert_called_once()
First of all, you may not need the burden of an external library such as pytest_mock, because pytest already got you covered using the integration with unittest.
You also do not need to use the usefixtures because whenever you need a fixture, you just receive it in your test method.
An ideal scenario based on your own code would look similar to this:
import pytest
from unittest.mock import patch
from com.abc.validation import Validation
class MyModule:
def __init__(self):
pass
def will_call_other_package(self):
val = Validation()
val.do()
def run(self):
self.will_call_other_package()
#pytest.fixture
def call_other_module():
with patch("my_package.my_module.MyModule.will_call_other_package") as _patched:
yield _patched
class TestMyModule:
def test_run_will_call_other_package(self, call_other_module):
call_other_module.assert_not_called()
obj = MyModule()
call_other_module.assert_not_called()
obj.run()
call_other_module.assert_called_once()
And also if you want to make sure that you did infact patch the target MyModule.will_call_other_package, modify your test like this:
class TestMyModule:
def test_run_will_call_other_package(self, call_other_module):
call_other_module.assert_not_called()
obj = MyModule()
call_other_module.assert_not_called()
obj.run()
call_other_module.assert_called_once()
assert False, (MyModule.will_call_other_package, call_other_module)
And you'll see something similar to this:
AssertionError: (<MagicMock name='will_call_other_package' id='140695551841328'>, <MagicMock name='will_call_other_package' id='140695551841328'>)
As you can see the id of both objects are the same, confirming our experiment was successful.
I have some unittests that I am running on a class, ClassA. This class has methods that call ClassB. ClassA and ClassB are in separate modules, class_A.py and class_B.py. Both of these modules have logger functions that are available globally within that module.
During my test of the methods of ClassA I want to suppress the logging messages. Using mock.patch it is straightforward to do this for the logger in class_A.py. But I have not found a way to patch the logger in class_B.py.
Here is some sample code:
unittest_file.py
import unittest
import class_A
class TestClassA(unittest.TestCase):
def setUp(self):
pass
def test_method(self):
my_class_A = class_A.ClassA()
my_class_A.run_method()
def tearDown(self):
pass
class_A.py
import logging
from class_B import ClassB
logger = logging.getLogger(__name__)
class ClassA:
run_method(self):
my_class_B = ClassB()
my_class_B.do_something()
logger.info("Running method on Class A")
class_B.py
import logging
logger = logging.getLogger(__name__)
class ClassB:
do_something(self):
logger.info("Doing something on Class B")
To patch the logger in class_A.py I can add #patch('__main__.unittest_file.ClassA.logger', autospec=True) as a decorator to the test_method() method.
But I don't have direct access to the logger in class_B.py to patch it as well.
The closest I have come to a solution is this:
#patch(eval('__main__.unittest_file.class_A.ClassB.__module__')['logger'])
but the eval() function applied to the module name doesn't preserve the import location, so it doesn't patch the logger in the right namespace.
I know I could easily solve this by changing the import statement in class_A.py to import class_B instead of from class_B import ClassB, but I don't want to edit the code in class_A.py or class_B.py, so that's not the answer I'm looking for.
I also know there are ways around this by disabling logging, as in this question but I am more interested in being able to fine-tune the control I have over different parts of the code to be tested, so that's not the answer I'm looking for either.
My question is:
Is there a way that I can navigate from an imported object to other objects in its parent module, while staying in the same namespace?
For me simply patching class_A.logger and class_B.logger works, e.g.
import unittest
from unittest import mock
class TestCaseA(unittest.TestCase):
#mock.patch('class_A.logger')
#mock.patch('class_B.logger')
def test_method(self, mockedA, mockedB):
my_class_A = ClassA()
my_class_A.run_method()
Both loggers don't output log messsages anymore.
I am having hard times with unittest mock and patch. I'd like to test a specific class and change some attributes in it.
I think that I have a path problem but can't figure how to resolve it.
#lib.inventory.py
class Inventory(object):
def __init__(self):
self.db = "12"
def do_stuff(self):
return "ok"
#test.py
import unittest
from mock import patch
import time
from lib.inventory import Inventory
#test_case.py
class MyTest(unittest.TestCase):
#patch('lib.inventory.Inventory')
def test_retrieve_project_id(self,mock_inventory):
print "####################"
print Inventory
print mock_inventory
print "####################"
When I run this test I should see that both Inventory and mock_inventory objects points to the same MagickMock Object. But it isn't the case :
####################
<class 'lib.inventory.Inventory'>
<MagicMock name='Inventory' id='140594977874320'>
####################
Could you tell me what I am doing wrong ?? Maybe I have misanderstood mock and patch concept ?
Thank you
I have a code in python as below:
import logging
def method():
value = get_value()
if value is not None:
do_something()
else:
logging.critical("value cannot be None")
I wanted to write a unit-test to cover both of the above scenarios. The first case for success was easy. But for the failure scenario, what is the correct way to catch that logging.critical was called?
I tried to put the method inside try-except block and it works.
... Mock required function and specify return values ...
try:
method()
except Exception as ex:
import traceback
traceback.format_exc()
Though the above method works, I don't think I correctly captured that logging.critical was called rather than an exception being raised. What is the correct way to go about it?
You could mock the logger. But, you are currently using the default logger, which you'd rather not mock. You could change your code to use a module specific logger:
import logging
logger = logging.getLogger(__name__)
def method():
value = get_value()
if value is not None:
return True
else:
logger.critical("value cannot be None")
Instead of method accessing that global logger object, you could pass a Logger object as an argument to method, or have a class around method and make a Logger object a class member, etc.
However, staying with the example as it is, you can then mock this logger object and assert that it is called as expected:
import unittest
import unittest.mock
class TestStringMethods(unittest.TestCase):
def test_method_withNone_shallLogCritical(self):
# setup
global logger
tmpLogger = logger
logger = unittest.mock.Mock()
# exercise
method()
# verify
logger.critical.assert_called() # python >= 3.6
# teardown
logger = tmpLogger