1

I have a python file : update_baseline_manifest.py. Now, I am writing unit tests and want to patch read_file and write_file and I have done it like

def update_baseline_manifest(baseline_manifest_path, vbf_info_map, logger):
    """ Updates manifest as per the part numbers in the vbfs_map """
    try:
        manifest_contents = read_file(baseline_manifest_path)
        // Do something


        write_file(manifest_contents, baseline_manifest_path)

def read_file(file_path):
    print('called read')
    with open(file_path, 'r') as file:
        return yaml.safe_load(file)


def write_file(contents, file_path):
    print('called write')
    with open(file_path, 'w') as file:
        yaml.dump(contents, file)

The test file looks like

from a.b.update_baseline_manifest import update_baseline_manifest

def test_update_baseline_manifest(self):
    test_contents = 'sample contents'

    with patch('update_baseline_manifest.read_file', return_value=test_contents) as \
        mock_read, patch('update_baseline_manifest.write_file') as mock_write:
        result = update_baseline_manifest(self.args.baseline_manifest_path,
                                            self.args.vbf_info_map,
                                            self.args.logger)

    mock_read.assert_called_with(self.args.baseline_manifest_path)
    mock_write.assert_called_with(contents_written, self.args.baseline_manifest_path)
    self.assertEqual(result, 0)

Now I can see the prints I added so it means the actual function was called and the mocked one wasn't. How do I patch them correctly so my mocked function gets called and not the actual one after importing the file at the top? I read a lot of posts about it but I cant get my head around it.

2 Answers2

1

You need to provide full path of the function you want to mock. Refer to: https://docs.python.org/3/library/unittest.mock.html#where-to-patch

    ...
    with patch('a.b.update_baseline_manifest.read_file', return_value=test_contents) as \
mock_read, patch('a.b.update_baseline_manifest.write_file') as mock_write:
    ...
Gameplay
  • 1,142
  • 1
  • 4
  • 16
0

I have tested your code in my system and as @Gameplay says the only problem is the path of the module that you use in the patch() instruction.

The most important modification

I have changed:

# YOUR
with patch('update_baseline_manifest.read_file', return_value=test_contents) as mock_read, \
     patch('update_baseline_manifest.write_file') as mock_write:

#+++++++++++++++++++++++++++++++++++++++++++++++++++++++
# TO MINE: I have added in the patch() instruction the
# path to reach the file update_baseline_manifest.py
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++
with patch('a.b.update_baseline_manifest.read_file', return_value=test_contents) as mock_read, \
     patch('a.b.update_baseline_manifest.write_file') as mock_write:

In fact you patch the objects point by the names update_baseline_manifest.read_file and update_baseline_manifest.write_file but when your test code invokes the function update_baseline_manifest (code under test), this function calls the real objects and not the mocked objects.


The rest of the answer details the code used to replicate the test in my system.

Your production code in my system

I have recreated your script update_baseline_manifest.py as followed:

def update_baseline_manifest(baseline_manifest_path, vbf_info_map, logger):
    """ Updates manifest as per the part numbers in the vbfs_map """
    try:
        manifest_contents = read_file(baseline_manifest_path)
        # Do something
        write_file(manifest_contents, baseline_manifest_path)
        return 0
    except Exception as ex:
        print(str(ex))

def read_file(file_path):
    print('called read')
    with open(file_path, 'r') as file:
        return yaml.safe_load(file)

def write_file(contents, file_path):
    print('called write')
    with open(file_path, 'w') as file:
        yaml.dump(contents, file)

I have added only some instructions in the function update_baseline_manifest:

  • return 0 to pass your test self.assertEqual(result, 0)
  • except Exception as ex: to complete your try: instruction

The file is saved in the path a/b/update_baseline_manifest.py where a and b contains the file __init__.py

Your test code in my system

My test code is the following:

import unittest
from unittest.mock import patch
from a.b.update_baseline_manifest import update_baseline_manifest

# utility class for not change your test code
class ARGS:
    baseline_manifest_path = "/path/to/manifest"
    vbf_info_map = "vbf info map"
    logger = "logger"

class MyTestCase(unittest.TestCase):

    args = ARGS()

    def test_update_baseline_manifest(self):
        test_contents = 'sample contents'
        with patch('a.b.update_baseline_manifest.read_file', return_value=test_contents) as mock_read \
             patch('a.b.update_baseline_manifest.write_file') as mock_write:
            result = update_baseline_manifest(self.args.baseline_manifest_path,
                                                self.args.vbf_info_map,
                                                self.args.logger)

        mock_read.assert_called_with(self.args.baseline_manifest_path)
        #mock_write.assert_called_with(contents_written, self.args.baseline_manifest_path)
        mock_write.assert_called_with(test_contents, self.args.baseline_manifest_path)
        self.assertEqual(result, 0)


if __name__ == '__main__':
    unittest.main()

In the test file I have added the following import:

import unittest
from unittest.mock import patch

I have also added the (utility) class ARGS to define the attribute arg of MyTestCase:

class ARGS:
    baseline_manifest_path = "/path/to/manifest"
    vbf_info_map = "vbf info map"
    logger = "logger"

I have modified one your test as followed:

# your test
#mock_write.assert_called_with(contents_written, self.args.baseline_manifest_path)

# my test
mock_write.assert_called_with(test_contents, self.args.baseline_manifest_path)

The last, but the most important modification (as I have written at the top of the answer):

I have changed the path inside the patch instructions.

The output

The output of the execution is:

.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

In the output is not present any printed message so we are sure that the production code is not invoked!


This link is very useful to understand where to patch.

Other useful examples are in the following posts:

frankfalse
  • 1,553
  • 1
  • 4
  • 17