Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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 
like image 468
jencoston Avatar asked Apr 11 '17 19:04

jencoston


People also ask

Can a constructor be mocked?

0, we can now mock Java constructors with Mockito. This allows us to return a mock from every object construction for testing purposes. Similar to mocking static method calls with Mockito, we can define the scope of when to return a mock from a Java constructor for a particular Java class.

How do you use mock exception in Python?

Just assign the exception to side_effect instead: mockedObj. raiseError. side_effect = Exception("Test") . You don't have to no: and his edit has a third way of doing it where he is making a Mock with the side effect set, but its still a valid, and good thing to know how to do in testing.


1 Answers

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.

like image 124
Martijn Pieters Avatar answered Oct 18 '22 14:10

Martijn Pieters