python-3.xmockingpython-unittest.mock

How to mock read and write a single file after updating its contents using mock_open in Python


  def simple_read_write():
    with open("file", "r") as f:
        data = json.load(f)

    data["change"] = "some content"

    with open("file", "w") as f:
        json.dump(data, f)

How to mock this scenario, where the same file is opened in read mode and its contents read. Then after changing the content, the same file is opened up again in write mode to update the contents.

I have hit a road-block for mocking this scenario using mock_open.

The function I implemented for the testing the above code is below. It gives me assertion error and I am not able to properly mock the read write scenarios of the same file.

class Example(unittest.TestCase):
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps({"change":"old data"}))
@patch("builtins.open", new_callable=mock_open)
def TestSimpleReadWrite(self, write_mock, read_mock):
    SampleFile.simple_read_write()

    read_mock.assert_called_with("file", "r")
    write_mock.assert_called_with("file", "w")
    write_mock().write.assert_called_with(json.dumps({"change": "some content"}))

Solution

  • I finally found the solution to my above problem.

    import unittest
    from unittest.mock import patch, call
    
    import check
    
    class Example(unittest.TestCase):
        @patch("json.load")
        @patch("json.dump")
        def test_simple_rtead_write(self, jdump_mock, jload_mock):
            with patch("builtins.open") as open_mock:
                jload_mock.return_value = {"change": "previous content", "prev": "yes"}
                jdump_mock.side_effect = [True]
                ret = check.simple_read_write()
                self.assertEqual(ret["change"], "some content")
                assert call("file", "r") in open_mock.call_args_list and \
                        call("file", "w") in open_mock.call_args_list
    
    
    if __name__ == "__main__":
        unittest.main()
    

    I just had to mock the json.load and json.dump and use return_value and side_effect respectively. Previously, I was making the mistake of adding two builtins.open instead of one. Since the same builtins.open mock will contain the call to both read and write, I just needed to use call to check if read and write has been performed or not.