How do I mock the test-only python constructor part?
I'm new to Python, so I apologize if this is a duplicate or too simple question. I wrote 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 am having a hard time figuring out the best way to do this. I was hoping I could create an alternate constructor that I could pass my mocked objects to, but that doesn't seem to work as I get an error that can't be resolved with test_mycoordinator. Am I going to test this class wrong? Is there a pythonic way that I should be testing it?
This is what my test class looks like:
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'm 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
source to share
There is no need to provide a separate constructor. Mockingly fixes your code to replace objects with mocks. Just use mock.patch()
decorator in your test methods; it will pass links to the generated layout objects.
Both producer.Producer()
and consumer.Consumer()
then striked out before instantiating:
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 is called test_send_data
, the code mock.patch()
replaces the link with the producer.Producer
mock object. Then your class MyCoordinator
uses these mock objects, not real code. the call producer.Producer()
returns a new layout of the object (the same object as the mock_producer.return_value
links), etc.
I made the assumption that producer
and consumer
are the names of the top-level modules. If not, provide the full import path. From the documentation mock.patch()
:
target should be a string in the form
'package.module.ClassName'
. The target is imported and the specified object is replaced with the new object, so the target must be imported from the environment you are calling frompatch()
. The target is imported when the decorated function is executed, not the decorating time.
source to share