Get value a method was called with during unit test - python

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/.

Related

MagicMock Mock'ed Function is not 'called'

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.

Python unittest test passed in class method is called with

I have a method that takes two inputs a emailer class and a dict of data.
def send_email(data, email_client):
**** various data checks and formatting *****
response_code = email_client.create_email(recipient=receipient
sender=sender
etc...)
I am trying to write a unit test that will assert email_client.create_email was called with the correct values based on the input data.
In my test file I have
from emailer.email import send_email
class TestEmails(unittest.TestCase):
def test_send_email(self):
email.send_email(get_transactional_email_data, MagicMock())
I normally test what a method is called with by something similar to:
mock.assert_called_with(recipient=receipient
sender=sender
etc..)
However, since this time I'm testing what a passed in class is being called with (and a MagicMock) I don't know how it should be done.
I don't think you need MagicMock. Just create the mocks upfront
from emailer.email import send_email
class TestEmails(unittest.TestCase):
def test_send_email(self):
myclient = Mock()
mycreate = Mock()
myclient.create_email = mycreate
email.send_email(get_transactional_email_data, myclient)
self.assertTrue(
mycreate.called_with(sender='...')
)

How do I mock part of a python constructor just for testing?

I am new to Python, so I apologize if this is a duplicate or overly simple question. I have written a coordinator class that calls two other classes that use the kafka-python library to send/read data from Kafka. I want to write a unit test for my coordinator class but I'm having trouble figuring out how to best to go about this. I was hoping that I could make an alternate constructor that I could pass my mocked objects into, but this doesn't seem to be working as I get an error that test_mycoordinator cannot be resolved. Am I going about testing this class the wrong way? Is there a pythonic way I should be testing it?
Here is what my test class looks like so far:
import unittest
from mock import Mock
from mypackage import mycoordinator
class MyTest(unittest.TestCase):
def setUpModule(self):
# Create a mock producer
producer_attributes = ['__init__', 'run', 'stop']
mock_producer = Mock(name='Producer', spec=producer_attributes)
# Create a mock consumer
consumer_attributes = ['__init__', 'run', 'stop']
data_out = [{u'dataObjectID': u'test1'},
{u'dataObjectID': u'test2'},
{u'dataObjectID': u'test3'}]
mock_consumer = Mock(
name='Consumer', spec=consumer_attributes, return_value=data_out)
self.coor = mycoordinator.test_mycoordinator(mock_producer, mock_consumer)
def test_send_data(self):
# Create some data and send it to the producer
count = 0
while count < 3:
count += 1
testName = 'test' + str(count)
self.coor.sendData(testName , None)
And here is the class I am trying to test:
class MyCoordinator():
def __init__(self):
# Process Command Line Arguments using argparse
...
# Initialize the producer and the consumer
self.myproducer = producer.Producer(self.servers,
self.producer_topic_name)
self.myconsumer = consumer.Consumer(self.servers,
self.consumer_topic_name)
# Constructor used for testing -- DOES NOT WORK
#classmethod
def test_mycoordinator(cls, mock_producer, mock_consumer):
cls.myproducer = mock_producer
cls.myconsumer = mock_consumer
# Send the data to the producer
def sendData(self, data, key):
self.myproducer.run(data, key)
# Receive data from the consumer
def getData(self):
data = self.myconsumer.run()
return data
There is no need to provide a separate constructor. Mocking patches your code to replace objects with mocks. Just use the mock.patch() decorator on your test methods; it'll pass in references to the generated mock objects.
Both producer.Producer() and consumer.Consumer() are then mocked out before you create the instance:
import mock
class MyTest(unittest.TestCase):
#mock.patch('producer.Producer', autospec=True)
#mock.patch('consumer.Consumer', autospec=True)
def test_send_data(self, mock_consumer, mock_producer):
# configure the consumer instance run method
consumer_instance = mock_consumer.return_value
consumer_instance.run.return_value = [
{u'dataObjectID': u'test1'},
{u'dataObjectID': u'test2'},
{u'dataObjectID': u'test3'}]
coor = MyCoordinator()
# Create some data and send it to the producer
for count in range(3):
coor.sendData('test{}'.format(count) , None)
# Now verify that the mocks have been called correctly
mock_producer.assert_has_calls([
mock.Call('test1', None),
mock.Call('test2', None),
mock.Call('test3', None)])
So the moment test_send_data is called, the mock.patch() code replaces the producer.Producer reference with a mock object. Your MyCoordinator class then uses those mock objects rather than the real code. calling producer.Producer() returns a new mock object (the same object that mock_producer.return_value references), etc.
I've made the assumption that producer and consumer are top-level module names. If they are not, provide the full import path. From the mock.patch() documentation:
target should be a string in the form 'package.module.ClassName'. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch() from. The target is imported when the decorated function is executed, not at decoration time.

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