Home > OS >  How to patch a method into a Mock()?
How to patch a method into a Mock()?

Time:09-22

I have a function I want to test. The datahandler argument is an instance of a class Datahandler which I have defined elsewhere in the module.

def create_row(row_content, datahandler):
  row = {}
  # Here comes some code that fills the "row" dictionary based on what was passed in row_content
  row = datahandler.process_row(row)
  # some more handling happens to the processed row 
  return row

I tried writing a unit test, but I am having difficulty, because I cannot seem to find a way to replace the process_row method of a mocked datahandler:

class TestCreateRow(unittest.TestCase):

  def fake_process_row(row): 
    return row

  def test_create_row(self):
    # create a suitable row_content here
    # row_content = ...
    expected = {"first column":"first value", "second column":"second value"}
    datahandler = Mock()
    # !!! This doesn't work - what is the correct solution?
    datahandler.process_row = self.fake_process_row
    result = create_row(row_content, datahandler)
    self.assertDictEqual(expected, result)

I experimented with using return_value and side_effects to assign the fake_process_row function to the mocked datahandler, and even to patch fake_process_row into Mock but with any synthactically correct version I tried, the call datahandler.process_row(row) continued to return a Mock(), when I need it to actually call my fake_process_row function so it can return a real row.

I have seen some solutions where in the test, a real datahandler object is created, and fake_process_row is patched into it. I don't want to do this, because the real class is not entirely trivial to instantiate. It would be much easier to just have a mock with one "working" method, the way I can have a mock with a "working" return value for a property.

CodePudding user response:

So I found out the solution, it was in creating two separate Mocks.

  def test_create_row(self):
    # create a suitable row_content here
    # row_content = ...
    expected = {"first column":"first value", "second column":"second value"}
    datahandler = Mock()
    # The magic happens here 
    method_mock = Mock(side_effect = self.fake_process_row)
    datahandler.process_row = method_mock
    result = create_row(row_content, datahandler)
    self.assertDictEqual(expected, result)

It was a bit counterintuitive to use side_effect, but it is what happens as soon as a Mock() is accessed. So when we set datahandler.process_row to a Mock with this side effect, it is triggered when the code under test calls the process_row method, and it is also smart enough to pass the arguments properly.

  • Related