python mock patch : a method of instance is called? - unit-testing

In python 2.7, I have this function
from slacker import Slacker
def post_message(token, channel, message):
channel = '#{}'.format(channel)
slack = Slacker(token)
slack.chat.post_message(channel, message)
with mock and patch, I can check that the token is used in Slacker class
import unittest
from mock import patch
from slacker_cli import post_message
class TestMessage(unittest.TestCase):
#patch('slacker_cli.Slacker')
def test_post_message_use_token(self, mock_slacker):
token = 'aaa'
channel = 'channel_name'
message = 'message string'
post_message(token, channel, message)
mock_slacker.assert_called_with(token)
how I can check the string use in post_message ?
I try with
mock_slacker.chat.post_message.assert_called_with('#channel_name')
but I get
AssertionError: Expected call: post_message('#channel_name')
Not called

You need to be specific about where the call is taking place. The code is:
slack.chat.post_message
So, as slack is an instance of the mocked class Slacker, you'll need to use return_value to ensure you're talking about that instance:
mock_slacker.return_value.chat.post_message.assert_called_with
You've patched 'slacker_cli.Slacker' so mock_slacker is a patched class. The call itself is taking place on an instance of that patched class. Calling a class returns an instance, hence the use of return_value.

Related

Object has no attribute '__bases__' when calling inspect.getmro()

I have a python class that inherits from storm.py from the Apache Storm MultiLang project.
My class looks like the following:
import storm
class MyClassName(Storm.Bolt):
def initialize(self,conf,context):
self._conf = conf;
self._context = context
def process(self, in_tuple):
storm.ack(in_tuple)
if __name__ == '__main__':
MyClassName().run()
I copied my python file (myfilename.py) out to /usr/lib64/python2.7/site-package. I then logged into the python shell and did an import myfilename. That completed without error. When I run the following inspect.getmro(myfilename.MyClassName()) I get the following error:
AttributeError: 'MyClassName' object has no attribute '__bases__'
I was under the impression that when I declared my class and passed it Storm.Bolt that I was extending Storm.Bolt. My questions are:
Do I need to define __bases__ in my class?
What else am I missing?
Using Python 2.7.13 on CentOs7. Storm version is 1.1.0
The inspect.getmro function expects its argument to be a class, but you're passing it an instance. Get rid of the parentheses that call the class and your code should work:
inspect.getmro(myfilename.MyClassName) # not MyClassName()!
If the call you gave in the question was a simplified example and you don't have the class directly available where you're calling getmro on the instance, you can use type to get the class:
obj = SomeClass() # this happens somewhere earlier on, and we don't know SomeClass below
inspect.getmro(type(obj)) # but we can easily get it using type()

request issue when unit-testing webapp2 with mock and patch

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.

python3 mock.assert_called_once_with on changing list [duplicate]

Consider example:
def func_b(a):
print a
def func_a():
a = [-1]
for i in xrange(0, 2):
a[0] = i
func_b(a)
And test function that tries to test func_a and mocks func_b:
import mock
from mock import call
def test_a():
from dataTransform.test import func_a
with mock.patch('dataTransform.test.func_b', autospec=True) as func_b_mock:
func_a()
func_b_mock.assert_has_calls([call(0), call(1)])
After func_a has executed I try to test if func_a made correct calls to func_b, but since in for loop I am mutating list in the end I get:
AssertionError: Calls not found.
Expected: [call(0), call(1)]
Actual: [call([1]), call([1])]
The following works (the importing mock from unittest is a Python 3 thing, and module is where func_a and func_b are):
import mock
from mock import call
import copy
class ModifiedMagicMock(mock.MagicMock):
def _mock_call(_mock_self, *args, **kwargs):
return super(ModifiedMagicMock, _mock_self)._mock_call(*copy.deepcopy(args), **copy.deepcopy(kwargs))
This inherits from MagicMock, and redefines the call behaviour to deepcopy the arguments and keyword arguments.
def test_a():
from module import func_a
with mock.patch('module.func_b', new_callable=ModifiedMagicMock) as func_b_mock:
func_a()
func_b_mock.assert_has_calls([call([0]), call([1])])
You can pass the new class into patch using the new_callable parameter, however it cannot co-exist with autospec. Note that your function calls func_b with a list, so call(0), call(1) has to be changed to call([0]), call([1]). When run by calling test_a, this does nothing (passes).
Now we cannot use both new_callable and autospec because new_callable is a generic factory but in our case is just a MagicMock override. But Autospeccing is a very cool mock's feature, we don't want lose it.
What we need is replace MagicMock by ModifiedMagicMock just for our test: we want avoid to change MagicMock behavior for all tests... could be dangerous. We already have a tool to do it and it is patch, used with the new argument to replace the destination.
In this case we use decorators to avoid too much indentation and make it more readable:
#mock.patch('module.func_b', autospec=True)
#mock.patch("mock.MagicMock", new=ModifiedMagicMock)
def test_a(func_b_mock):
from module import func_a
func_a()
func_b_mock.assert_has_calls([call([0]), call([1])])
Or:
#mock.patch("mock.MagicMock", new=ModifiedMagicMock)
def test_a():
with mock.patch('module.func_b') as func_b_mock:
from module import func_a
func_a()
func_b_mock.assert_has_calls([call([0]), call([1])])

Assert that logging has been called with specific string

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')

Mock object issue

I am using mock from voidspace and trying to execute some tests using unittest2 and the behaviour is strange. When i use "#patch.object(Test,'asd')" as a patch i get the mock object in the function arguments.
If i use #patch.object(Test,'asd',new_fun) as patch i dont get the one of the parameters.
I am using mock-1.0.1
Here you can see a small sample of code that exemplifies the problem.
I want to try to understand if this issue is a problem with the way i do the patch or if this is a problem in the library.
import unittest2 as unittest
from mock import patch
class Test:
def asd(self, a, b =""):
print "1"
class Test1:
def kk(self, l):
Test().asd("1")
def kk1(self, l):
Test().asd("1","1")
#patch.object(Test,'asd')
class MockClassUT(unittest.TestCase):
def test_stat_process(self, my_mock):
t = Test1()
def test_stat_process1(self, my_mock):
t = Test1()
def test_stat_process2(self, my_mock):
t = Test1()
def new_fun(*args, **kwargs):
print "1"
#patch.object(Test,'asd',new_fun)
class MockClassUT1(unittest.TestCase):
def test_stat_process(self, my_mock):
t = Test1()
t.kk("1")
my_mock.assert_called_with("k")
testloader = unittest.TestLoader()
testnames = testloader.getTestCaseNames(MockClassUT)
suite = unittest.TestSuite()
for name in testnames:
suite.addTest(MockClassUT(name))
testnames = testloader.getTestCaseNames(MockClassUT1)
for name in testnames:
suite.addTest(MockClassUT1(name))
print testnames
unittest.TextTestRunner(verbosity=2).run(suite)
This is expected behaviour. You have mocked it as a class decorator and you've also specified the new function new_fun. As such the mocked object won't be passed to each of the methods in the test class.
This means you can't expect my_mock as parameter there and it also means you can't write assertions using my_mock.
Furthermore, as an aside, your mocked method new_fun doesn't have the same signature as the method you're mocking (asd). The method asd expects self, a, b="" whereas new_fun doesn't have arguments so I expect an issue to come up there as well when the mocked method is called.