I'm trying to use unittest to test some functions of a SimpleXMLRPCServer I made. Togethere with Mock, I'm now trying to assert that a specific message has been logged when an if statement is reached, but I can't get it to work. I've tried implementing various answers I found here on StackOverflow or by Googling, but still no luck. The calls I make in the Test Case are as follows:
def test_listen_for_tasks(self):
el = {'release': 'default', 'component': None}
for i in range(50):
self.server._queue.put(el)
ServerThread.listen_for_tasks(self.server, 'bla', 'blabla')
with mock.patch('queue_server.logging') as mock_logging:
mock_logging.warning.assert_called_with('There are currently {}'
' items in the queue'.format(
str(len(self.server._queue.queue))))
The function in the server is as follows:
def listen_for_tasks(self, release, component):
item = {'release': release, 'component': component}
for el in list(self._queue.queue):
if self.is_request_duplicate(el, item):
logger.debug('Already have a request'
' for this component: {}'.format(item))
return
self._queue.put(item, False)
if len(self._queue.queue) > 50:
logger.warning('There are currently {}'
' items in the queue'.format(
str(len(self._queue.queue))))
Any idea why this is not working? I'm new to unit testing in Python and asserting that a logger has done something seems the biggest problem one could face, so I might have screwed up with something really simple in the code. Any kind of help will be greatly appreciated!
EDIT: for completeness, here's the test output and failure:
.No handlers could be found for logger "queue_server"
F
FAIL: test_listen_for_tasks (__main__.TestQueueServer)
Traceback (most recent call last):
File "artifacts_generator/test_queue_server.py", line 46, in test_listen_for_tasks
str(len(self.server._queue.queue))))
File "/home/lugiorgi/Desktop/Code/publisher/env/local/lib/python2.7/site-packages/mock/mock.py", line 925, in assert_called_with
raise AssertionError('Expected call: %s\nNot called' % (expected,))
AssertionError: Expected call: warning('There are currently 51 items in the queue')
Not called
Ran 2 tests in 0.137s
FAILED (failures=1)
Since python 3.4 you can use unittest.TestCase class method assertLogs
import logging
import unittest
class LoggingTestCase(unittest.TestCase):
def test_logging(self):
with self.assertLogs(level='INFO') as log:
logging.info('Log message')
self.assertEqual(len(log.output), 1)
self.assertEqual(len(log.records), 1)
self.assertIn('Log message', log.output[0])
You need to first mock the object, then call the function you want to test.
When mocking the object, you also need to provide the full package and object/function name of the object you are mocking, not a variable name.
Finally, it's often more convenient to use the decorator form of patch.
So, for example:
logger = logging.getLogger(__name__)
def my_fancy_function():
logger.warning('test')
#patch('logging.Logger.warning')
def test_my_fancy_function(mock):
my_fancy_function()
mock.assert_called_with('test')
# if you insist on using with:
def test_my_fancy_function_with_with():
with patch('logging.Logger.warning') as mock:
my_fancy_function()
mock.assert_called_with('test')
Related
I am not sure about the title of this question, as it is not easy to describe the issue with a single sentence. If anyone can suggest a better title, I'll edit it.
Consider this code that uses smbus2 to communicate with an I2C device:
# device.py
import smbus2
def set_config(bus):
write = smbus2.i2c_msg.write(0x76, [0x00, 0x01])
read = smbus2.i2c_msg.read(0x76, 3)
bus.i2c_rdwr(write, read)
I wish to unit-test this without accessing I2C hardware, by mocking the smbus2 module as best I can (I've tried mocking out the entire smbus2 module, so that it doesn't even need to be installed, but had no success, so I'm resigned to importing smbus2 in the test environment even if it's not actually used - no big deal so far, I'll deal with that later):
# test_device.py
# Depends on pytest-mock
import device
def test_set_config(mocker):
mocker.patch('device.smbus2')
smbus = mocker.MagicMock()
device.set_config(smbus)
# assert things here...
breakpoint()
At the breakpoint, I'm inspecting the bus mock in pdb:
(Pdb) p smbus
<MagicMock id='140160756798784'>
(Pdb) p smbus.method_calls
[call.i2c_rdwr(<MagicMock name='smbus2.i2c_msg.write()' id='140160757018400'>, <MagicMock name='smbus2.i2c_msg.read()' id='140160757050688'>)]
(Pdb) p smbus.method_calls[0].args
(<MagicMock name='smbus2.i2c_msg.write()' id='140160757018400'>, <MagicMock name='smbus2.i2c_msg.read()' id='140160757050688'>)
(Pdb) p smbus.method_calls[0].args[0]
<MagicMock name='smbus2.i2c_msg.write()' id='140160757018400'>
Unfortunately, at this point, the arguments that were passed to write() and read() have been lost. They do not seem to have been recorded in the smbus mock and I've been unable to locate them in the data structure.
Interestingly, if I break in the set_config() function, just after write and read assignment, and inspect the mocked module, I can see:
(Pdb) p smbus2.method_calls
[call.i2c_msg.write(118, [160, 0]), call.i2c_msg.read(118, 3)]
(Pdb) p smbus2.method_calls[0].args
(118, [160, 0])
So the arguments have been stored as a method_call in the smbus2 mock, but not copied over to the smbus mock that is passed into the function.
Why is this information not retained? Is there a better way to test this function?
I think this can be summarised as this:
In [1]: from unittest.mock import MagicMock
In [2]: foo = MagicMock()
In [3]: bar = MagicMock()
In [4]: w = foo.write(1, 2)
In [5]: r = foo.read(1, 2)
In [6]: bar.func(w, r)
Out[6]: <MagicMock name='mock.func()' id='140383162348976'>
In [7]: bar.method_calls
Out[7]: [call.func(<MagicMock name='mock.write()' id='140383164249232'>, <MagicMock name='mock.read()' id='140383164248848'>)]
Note that the bar.method_calls list contains calls to the functions .write and .read (good), but the parameters that were passed to those functions are missing (bad). This seems to undermine the usefulness of such mocks, since they don't interact as I would expect. Is there a better way to handle this?
The reason you can't access the calls to write and read is that they themselves are the return_value of another mock. What you are trying to do is access the mock "parent" (Using the terminology here: https://docs.python.org/3/library/unittest.mock.html).
It actually is possible to access the parent, but I'm not sure it's a good idea, since it used an undocumented and private attribute of the MagicMock object, _mock_new_parent.
def test_set_config(mocker):
"""Using the undocumented _mock_new_parent attribute"""
mocker.patch('device.smbus2')
smbus = mocker.MagicMock()
device.set_config(smbus)
# Retrieving the `write` and `read` values passed to `i2c_rdwr`.
mocked_write, mocked_read = smbus.i2c_rdwr.call_args[0]
# Making some assertions about how the corresponding functions were called.
mocked_write._mock_new_parent.assert_called_once_with(0x76, [0x00, 0x01])
mocked_read._mock_new_parent.assert_called_once_with(0x76, 3)
You can check that the assertions work by using some bogus values instead, and you'll see the pytest assertion errors.
A simpler, and more standard approach IMO is to look at the calls from the module mock directly:
def test_set_config_2(mocker):
""" Using the module mock directly"""
mocked_module = mocker.patch('device.smbus2')
smbus = mocker.MagicMock()
device.set_config(smbus)
mocked_write = mocked_module.i2c_msg.write
mocked_read = mocked_module.i2c_msg.read
mocked_write.assert_called_once_with(0x76, [0x00, 0x01])
mocked_read.assert_called_once_with(0x76, 3)
I just realized that you use dependency injection and that you should take advantage of this.
This would be the clean approach.
Mocks can behave unexpected/nasty (which does not mean that they are evil - only sometime.... counterintuitive)
I would recommend following test structure:
# test_device.py
import device
def test_set_config():
dummy_bus = DummyBus()
device.set_config(dummy_bus)
# assert things here...
assert dummy_bus.read_data == 'foo'
assert dummy_bus.write_data == 'bar'
breakpoint()
class DummyBus:
def __init__(self):
self.read_data = None
self.write_data = None
def i2c_rdwr(write_input, read_input):
self.read_data = read_input
self.write_data = write_input
For anyone coming across this, I posed a variation of this problem in another question, and the result was quite satisfactory:
https://stackoverflow.com/a/73739343/
In a nutshell, create a TraceableMock class, derived from MagicMock, that returns a new mock that keeps track of its parent, as well as the parameters of the function call that led to this mock being created. Together, there is enough information to verify that the correct function was called, and the correct parameters were supplied.
I am working on some code (not mine) and I try to instand it.
There 16 classes in the inheritance tree.
Here area the class according to inspect.getmro(self.__class__)
foo_barcheck.views.barcheck.AdminListActionView
foo_barcheck.views.barcheck.ListActionMixin
djangotools.utils.urlresolverutils.UrlMixin
foo.views.ListActionView
foo.views.ContextMixin
foo.views.fooMixin
djangotools.views.ListActionView
djangotools.views.ListViewMixin
django.views.generic.list.MultipleObjectMixin
django.views.generic.edit.FormMixin
djangotools.views.DTMixin
django.views.generic.base.TemplateView
django.views.generic.base.TemplateResponseMixin
django.views.generic.base.ContextMixin
django.views.generic.base.View
object
I want to trace what happens if I call self.do_magic().
I want to see all do_magic() calls of these 16 classes.
The ideal solution would look like this:
result_of_do_magic, list_of_do_magic_methods = trace_method_calls(self.do_magic)
I have no clue how to implement trace_method_calls(). It should execute the code and trace the method calls at runtime.
Is there a tracing guru which knows how to do this?
AFAIK this needs to be done at runtime, since I don't know if all methods calls the do_magic() of the parents via super().
Update
I don't want to modify the code to be able to trace it. I guess this should be possible, since the mocking library can do comparable magic.
If I understand correctly, I can see 2 solutions here.
1) [Brute-ish and relatively easy] Find all occurrences of "do_magic" in your code and wrap it with decorator like:
def trace_this(fn):
def wrapped(*args, **kwargs):
print("do_magic is called!")
result = fn(*args, **kwargs)
print("Result is:{0}".format(result))
return result
return wrapped
...
#trace_this
def do_magic():
...
2) Run django as:
python -m trace -t manage.py runserver 127.0.0.1:8000 --noreload > trace.txt
and all invoked code would be in trace.txt that you can then parse/analyze with tools you prefer.
According to my understanding what you want are the outer frames of a particular function call to see what classes were called to lead to the method in question.
If it is what you want continue below, else let me know in the comments:
Below is an POC to demonstrate how it works. You can build a decorator out of it.
from __future__ import print_function
import inspect
class A:
def a(self):
stacks = inspect.getouterframes(inspect.currentframe())
for stack in stacks:
frame = stack[0]
klass = frame.f_locals.get("self")
mthd = frame.f_code.co_name
if klass: # to avoid printing modules
print('Called by class: {0}, method: {1}'.format(klass.__class__.__name__, mthd))
class B:
def b(self):
bb = A()
bb.a()
class C:
def c(self):
cc = B()
cc.b()
z = C()
z.c()
Output:
Explanation:
Class C calls Class B which in turn calls Class A
When the execution reaches till A.a() all the classes that inherited A are in the outer frames.
You can extract out just the functionality of A.a() to carve out a decorator so that it will print all outer frames leading upto the function call.
I'm building unit tests for this webapp2 handler (built for GAE)
class PushNotificationHandler(webapp2.RequestHandler):
def post(self):
UserNotification.parse_from_queue(self.request)
return
app = webapp2.WSGIApplication([
(r'/push/notification', PushNotificationHandler),
], debug=True)
One test is
#patch.object(UserNotification, 'parse_from_queue')
def test_post_webapp(self, p_parse_from_queue):
response = webtest.TestApp(app).post('/push/notification')
eq_(response.status_int, 200)
p_parse_from_queue.assert_called_once_with(response.request)
The HTTP reply is OK, but the mock assertion fails:
Expected call: parse_from_queue(<TestRequest at 0x105a89850 POST http://localhost/push/notification>)
Actual call: parse_from_queue(<Request at 0x105a89950 POST http://localhost/push/notification>)
I can't understand why the request is not the correct one (looks like a deep copy). Is there anything wrong with the unit-test, or is that the way webapp2 handle requests. In the second case, is there a way to test it, without creating a separate test to test PushNotificationHandler.post()
Thanks
I've used mock's call_args in a similar situation. You can do something like this:
request = p_parse_from_queue.call_args[0][0]
self.assertEqual(request.url, "foo")
self.assertEqual(request.*, *)
The [0][0] gives you the first passed argument assuming that you are using ordered arguments and not keyword arguments.
You can then proceed to check other relevant attributes of the request object to make sure it is behaving as expected.
I have been exploring mock and pytest for a few days now.
I have the following method:
def func():
if not os.path.isdir('/tmp/folder'):
os.makedirs('/tmp/folder')
In order to unit test it, I have decided to patch os.path.isdir and os.makedirs, as shown:
#patch('os.path.isdir')
#patch('os.makedirs')
def test_func(patch_makedirs, patch_isdir):
patch_isdir.return_value = False
assert patch_makedirs.called == True
The assertion fails, irrespective of the return value from patch_isdir. Can someone please help me figure out where I am going wrong?
Can't say for sure having the complete code, but I have the feeling it's related to where you're patching.
You should patch the os module that was imported by the module under test.
So, if you have it like this:
mymodule.py:
def func():
if not os.path.isdir('/tmp/folder'):
os.makedirs('/tmp/folder')
you should make your _test_mymodule.py_ like this:
#patch('mymodule.os')
def test_func(self, os_mock):
os_mock.path.isdir.return_value = False
assert os_mock.makedirs.called
Note that this specific test is not that useful, since it's essentially testing if the module os works -- and you can probably assume that is well tested. ;)
Your tests would probably be better if focused on your application logic (maybe, the code that calls func?).
You are missing the call to func().
#patch('os.path.isdir')
#patch('os.makedirs')
def test_func(patch_makedirs, patch_isdir):
patch_isdir.return_value = False
yourmodule.func()
assert patch_makedirs.called == True
I'm using the django test suite with nose and trying to figure out how I can run a followup test if a previous test fails. Is that possible?
Here is how I'd do it:
Have a global variable called test_condition_a = False shared among test cases.
Try/catch assert in test_a and catch the exception when it fails so that you can set test_condition_a = True before the test exits.
Use #unittest.skipIf(test_condition_a) on all other test cases that you want run only if test_a fails.
EDIT On second thought, the above is not going to work as the test order is random. Your best bet would be to do something like this
class Test(TestCase):
def setUp(self):
...
#unittest.skip("Skip by default")
def testB(self):
#test code
def testA(self):
try:
#test code
return True
except Error:
return False
def testA_then_B(self):
if (self.testA()):
self.testB()