MagicMock Mock'ed Function is not 'called' - python

I an trying to mock an api function send_message from stmplib.SMTP in Python using MagicMock.
a simplified version of my function looks like
#email_sender.py
def sendMessage():
msg = EmailMessage()
#Skipping code for populating msg
with smtplib.SMTP("localhost") as server:
server.send_message(msg)
I want to mock server.send_message call for my unit test. I have searched SO for some pointers and tried to follow a similar question.
Here is my unit test code based on the above question:
#email_sender_test.py
import email_sender as es
def test_send_message_input_success() -> None:
with patch("smtplib.SMTP", spec=smtplib.SMTP) as mock_client:
mock_client.configure_mock(
**{
"send_message.return_value": None
}
)
es.sendMessage()
#assert below Passes
assert mock_client.called == True
#assert below Fails
assert mock_client.send_message.called == True
Any idea what I am doing wrong which is causing the assert mock_client.send_message.called == True to fail?
Thanks!

You are testing the wrong mock attribute. Your code doesn’t call smtplib.SMTP.send_message. It might do so indirectly with the real object but a Mock can’t actually be sure of that nor should you want it to be that magical.
Take a look at what your code does with the smtp.SMTP mock; this is important because you’ll need to follow the same path in your test setup:
the mock is called: smtplib.SMTP("localhost"). Calling a mock will return a new mock object to stand in as the instance.
the instance mock is used as a context manager: with ... as server:. Python calls the __enter__() method and the return value of that method is bound to the name given by as. The instance mock will produce a new mock object for this. In fact, even the .__enter__ attribute is a mock object standing in for the method object.
The context manager mock, finally, is used to send the message: server.send_message(...).
The mock library actually creates these extra mock objects just once, then reuses them whenever needed, and you can access them in your setup, via the Mock.return_value attributes. You can use these to follow the same trail:
with patch("smtplib.SMTP", spec=smtplib.SMTP) as mock_smtp:
mock_instance = mock_smtp.return_value # smtplib.SMTP("localhost")
mock_cm = mock_instance.__enter__.return_value # with ... as server
# set up the server.send_message call return value
mock_cm.send_message.return_value = None
# run the function-under-test
es.sendMessage()
# assert things were called
assert mock_cm.send_message.called
Note: there is no need to add == True; that’s just doubling up on testing if true is true to produce true. assert already takes care of the check for you.
You can try these things out in an interactive interpreter too:
>>> import smtplib
>>> from unittest.mock import patch
>>> patcher = patch("smtplib.SMTP", spec=smtplib.SMTP)
>>> mock_smtp = patcher.start() # same as what with patch() as ... does
>>> smtplib.SMTP
<MagicMock name='SMTP' spec='SMTP' id='4356608448'>
>>> smtplib.SMTP("localhost")
<NonCallableMagicMock name='SMTP()' spec='SMTP' id='4356726496'>
>>> with smtplib.SMTP("localhost") as server:
... pass
...
>>> server
<MagicMock name='SMTP().__enter__()' id='4356772768'>
>>> server.send_message("some argument") # not set to return None here
<MagicMock name='SMTP().__enter__().send_message()' id='4356805728'>
>>> mock_smtp.return_value.__enter__.return_value.send_message.called
True
Note how each new mock object has a name that reflects how we got there! For future tests, try printing mocked objects in your code-under-test to see how to reach the right objects in your test setup, or use a debugger to inspect the objects. It’ll help you figure out what is going on.

Related

Pytest - mocking a side_effect on mock's nested attribute function / method

I have a fixture mocking an external library like so, using pytest-mock, which is a wrapper around unittest.mock.
# client.py
import Test as TestLibrary
class LibraryName():
def get_client():
return TestLibrary.Library()
# library_service.py
def using_library():
'''
Edited note: Library().attribute behind the scenes is set to
self.attribute = Attribute()
so this may be affecting the mocking
'''
client = LibraryName.get_client()
return client.attribute.method()
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
# test_library_service.py
def test_library_method(library_client_mock):
result = using_library()
I can mock a return value like so:
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
result = using_library()
assert result == "test"
but I can't mock throwing an Exception with side_effect
def test_library_method(library_client_mock):
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError # doesn't work
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError() # doesn't work
attrs = { 'attribute.method.side_effect': TypeError }
library_client_mock.configure_mock(**attrs) # doesn't work
with pytest.raises(TypeError):
using_library() # fails assertion
what I missing here?
These are the errors in your code:
Change:
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
To:
library_client_mock.return_value.attribute.method.return_value = "test"
Change:
library_client_mock.return_value.attribute.return_value.method.side_effect = TypeError
To:
library_client_mock.return_value.attribute.method.side_effect = TypeError
Explanation
The .return_value must only be used for callable objects e.g. a function as documented:
return_value
Set this to configure the value returned by calling the mock:
>>> mock = Mock()
>>> mock.return_value = 'fish'
>>> mock()
'fish'
Thus, you can use .return_value only for the following:
TestLibrary.Library()
TestLibrary.Library().attribute.method()
But not for:
TestLibrary.Library().attribute
Because .attribute is not a callable e.g. TestLibrary.Library().attribute().
Warning
The way you are patching Library is via its source location at Test.Library (or aliased as TestLibrary.Library). specifically via:
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
It works currently because the way you import and use it is via the root path.
# client.py
import Test as TestLibrary
...
return TestLibrary.Library()
...
But if we change the way we imported that library and imported a local version to client.py:
# client.py
from Test import Library # Instead of <import Test as TestLibrary>
...
return Library() # Instead of <TestLibrary.Library()>
...
It will now fail. Ideally, you should patch the specific name that is used by the system under test, which here is client.Library.
import client
return mocker.patch.object(client, 'Library')
Unless you are sure that all files that will use the library will import only the root and not a local version.
#Niel Godfrey Ponciano set me on the right path with this syntax for the side_effect
library_client_mock.return_value.attribute.method.side_effect = TypeError
but it wasn't enough.
In
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
return mocker.patch.object(TestLibrary, 'Library')
I had to add an extra mock:
# conftest.py
#pytest.fixture
def library_client_mock(mocker):
import Test as TestLibrary
mock_library_client = mocker.patch.object(TestLibrary, 'Library')
# option 1
mock_attribute = Mock()
# option 2, path to Library.attribute = Attribute()
mock_attribute = mocker.patch.object(TestLibrary.services, 'Attribute', autospec=True)
mock_library_client.attach_mock(mock_attribute, "attribute")
return mock_library_client
and then both of the following statements worked as expected. Although I am not sure why return_value works out of the box without an attached mock, but side_effect does not.
# return_value set correctly
# NOTE return_value needed after each
library_client_mock.return_value.attribute.return_value.method.return_value = "test"
# side_effect set correctly
# NOTE return_value not needed after "attribute"
library_client_mock.return_value.attribute.method.side_effect = TypeError

Get value a method was called with during unit test

I am using pytest_mock to mock a function call. I would like to inspect the call to doB() to see if it was called with the value 3. How would I write the assert for this?
def testtest(mocker):
# arrange
mocker.patch.object(ClassA, 'doB', return_value=None)
sut = ClassA()
# act
actual = sut.party(4) # will make a call to doB
expected = 3
# assert
Maybe something like this:
def testtest(mocker):
# arrange
mocked_doB = mocker.patch.object(ClassA, 'doB', return_value=None)
sut = ClassA()
# act
actual = sut.party(4) # will make a call to doB
expected = 3
# assert
mocked_doB.assert_called_once_with(expected)
If you assign a value to the mocked object, in this case variable mocked_doB, you will get a MagicMock object back. With this MagicMock object, you have access to all kind of methods from the unittest.mock Mock class, e.g.
assert_called_once_with()
call_count,
and many more...
See here: https://docs.python.org/3/library/unittest.mock.html#the-mock-class
This works because with the pytest-mock framework you can directly access the mock module from mocker.
This is stated here under "Usage", https://pypi.org/project/pytest-mock/.

How to patch a function that a Flask view calls

My webapp wants to send a message to AWS SQS with boto and I'd want to mock out sending the actual message and just checking that calling send_message is called. However I do not understand how to use python mock to patch a function that a function being tested calls.
How could I achieve mocking out boto con.send_message as in the pseudo-like code below?
views.py:
#app.route('/test')
def send_msg():
con = boto.sqs.connect_to_region("eu-west-1",aws_access_key_id="asd",aws_secret_access_key="asd")
que = con.get_queue('my_queue')
msg = json.dumps({'data':'asd'})
r=con.send_message(que, msg)
tests.py
class MyTestCase(unittest.TestCase):
def test_test(self):
with patch('views.con.send_message') as sqs_send:
self.test_client.get('/test')
assert(sqs_send.called)
To do this kind of test you need patch connect_to_region(). When this method is patched return a MagicMock() object that you can use to test all your function behavior.
Your test case can be something like this one:
class MyTestCase(unittest.TestCase):
#patch("boto.sqs.connect_to_region", autospec=True)
def test_test(self, mock_connect_to_region):
#grab the mocked connection returned by patched connect_to_region
mock_con = mock_connect_to_region.return_value
#call client
self.test_client.get('/test')
#test connect_to_region call
mock_connect_to_region.assert_called_with("eu-west-1",aws_access_key_id="asd",aws_secret_access_key="asd")
#test get_queue()
mock_con.get_queue.assert_called_with('my_queue')
#finaly test send_message
mock_con.send_message.assert_called_with(mock_con.get_queue.return_value, json.dumps({'data':'asd'}))
Just some notes:
I wrote it in a white box style and check all calls of your view: you can do it more loose and omit some checks; use self.assertTrue(mock_con.send_message.called) if you want just check the call or use mock.ANY as argument if you are not interested in some argument content.
autospec=True is not mandatory but very useful: take a look at autospeccing.
I apologize if code contains some error... I cannot test it now but I hope the idea is clear enough.

How to mock path like `module.Class`? using Python's Mock module?

How do I mock a class with this path ablib.Pin? And how do I mock the attributes of an instance off Pin? I would say it should work like this:
mock = MagicMock()
mock.Pin = MagicMock()
mock.Pin.kernel_id = 'N21'
mock.Pin.set_value.return_value = True
mock.Pin.get_value.return_value = 3
modules = {
'ablib': mock,
'ablib.Pin': mock.Pin,
}
patcher = patch.dict('sys.modules', modules)
patcher.start()
When I create a Pin instance I and call get_value or set_value I get a MockInstance instead of True or 3.
>>> p = Pin()
>>> p.set_value(3)
<MagicMock name='mock.Pin().set_value(3)' id='47965968'>
>>> p.kernel_id
<MagicMock name='mock.Pin().kernel_id' id='49231056'>
When I call kernel_id directly on Pin I get the result I need.
>>> Pin.kernel_id
'N21'
How do I mock ablib.Pin in a way that I get the values I want from an instance ablib.Pin()
Here is the working version with minimal changes to your code:
from unittest.mock import patch, MagicMock
mock = MagicMock()
mock.Pin().kernel_id = 'N21'
mock.Pin().set_value.return_value = True
mock.Pin().get_value.return_value = 3
modules = {
'ablib': mock
}
patcher = patch.dict('sys.modules', modules)
patcher.start()
Test:
>>> import ablib
>>> ablib.Pin
<MagicMock name='mock.Pin' id='139917634188240'>
>>> ablib.Pin
<MagicMock name='mock.Pin' id='139917634188240'>
>>> ablib.Pin()
<MagicMock name='mock.Pin()' id='139917634233616'>
>>> ablib.Pin()
<MagicMock name='mock.Pin()' id='139917634233616'>
>>> p = ablib.Pin()
>>> p.set_value(3)
True
>>> p.kernel_id
'N21'
Notice that I've removed mock.Pin = MagicMock() and removed mock.Pin from the dictionary. New MagicMock instance is created by default when you access existing MagicMock attribute or call it. The same instance will be returned next time you do that action. So there is no need to explicitly create MagicMock for mock.Pin (for Pin class), mock.Pin() (for Pin instance) and to register them with patch.dict(). Just registering mock is enough.
Thanks to patch.dict() it works even when module ablib doesn't exist. I believe that it is not possible to achieve with patch() and patch.object(). Nevertheless for real unit-testing I use patch() to mock a class and patch.object() to mock attributes of an instance for some particular test or group of tests. I have not been in a situation when I needed to mock a whole module, but I'm quite new to unit-testing and Python.

How to use unittest.mock to mock arbitrary ConfigParser calls in a unit test

I'm trying to start using unittest.mock's action/assert pattern instead of mox's record/replay/verify pattern.
# foo.py
def op_1(param):
pass
def op_2(param):
pass
def do_stuff(param_1, param_2):
global config
global log
try:
op_1(param_1)
if config.getboolean('section','option'):
op_2(param_2)
except:
log.error("an error occured")
And, here's an example of what my unittest file looks like.
# test_foo.py
class TestFoo(unittest.TestCase):
def test_do_stuff(self):
param_1 = None
param_2 = None
foo.config = MagicMock()
foo.config.getboolean('section','option', return_value = True)
foo.op_1 = MagicMock()
foo.op_2 = MagicMock()
do_stuff(param_1, param_2)
foo.op_1.assert_called_once_with(param_1)
foo.op_2.assert_called_once_with(param_2)
foo.config.getboolean.assert_called_once_with('section','option')
Does this test to verify the items below/am I using mock right?
do_stuff call returned without error
op_1 was called with param_1
op_2 was called with param_2
config parser object had been used, but the specific calls don't matter
It turns out that I was using the return_value wrong.
When I need a mock.Mock or mock.MagicMock object to return a value, it will need to always return that value, regardless of the arguments passed. Though, it might be nice to give different behavior based on arguments passed (possible feature request).
The way I completed this was:
foo.config.getboolean = mock.MagicMock(return_value = True)
And then I can do this:
self.assertGreaterThan(len(foo.config.mock_calls), 0)
self.assertGreaterThan(len(foo.config.getboolean(str(),str())), 0)

Categories