Unrecognized Escape Sequence

An increasingly grumpy blog about software engineering

More Python mocking fun

This post is a collection of python unit testing ‘gotchas’.

Mocking external packages

import sys
import unittest
from unittest.mock import patch, Mock, call, mock_open
from io import StringIO, TextIOWrapper

mock_os = Mock()
sys.modules["os"] = mock_os

from config_editor import ConfigEditor

Now we have effectively mocked all actual file I/O in our tests

Mocking in-built methods and file I/O

Super useful if you want to test a unit that uses the builtin open method. The following example is a class for modifyinh a system config file, but the unit tests never need touch the file system. The setUp defines the initial state of the file, and then each test can evaluate its expected and final state.

class ConfigEditorTestCase(unittest.TestCase):

    def setUp(self):

        # Create the object to test

        self.config_editor = ConfigEditor("/test/file.config")

        self.mock_input_file = StringIO("# Title\n"
                                        "#turn_on_rocket_launcher=123\n"
                                        "### turn_on_piranha_launcher=123\n"
                                        "\n"
                                        "enable_flamethrower=true\n"
                                        "enable_a_thing=laser_beam\n"
                                        "#enable_a_thing=blaster_power=500\n"
                                        "enable_a_thing=photon_beam\n"
                                        "\n"
                                        "###############################\n"
                                        "# Some random comment\n"
                                        "###############################\n"
                                        "set_this=thing_to=50000\n")

        # Create an empty stream for the output file which we can then inspect

        self.mock_output_file = StringIO()

        # Patch the in-built "open" method, so it returns our dummy streams

        self.mock_open = mock_open()
        self.mock_open_method = patch("builtins.open", self.mock_open)
        self.mock_open.side_effect = [self.mock_input_file, self.mock_output_file]
        self.mock_open_method.start()

Patching objects

Python offers a few ways of patching objects so that they can be used for unit testing. Often you see the with patch() notation to achieve this in a local scope:

    with patch('module.Foo') as mock:
        instance = mock.return_value
        instance.method.return_value = 'the result'
        result = some_function()
        assert result == 'the result'

Or sometimes in the form of a method decorator:

    @mock.patch("os.listdir", mock.MagicMock(return_value="some_directory_name"))
    def test1():
        assert "some_directory_name" == os.listdir()

But sometimes you want to patch an object for whole fixture without decorating every test in the same way. Here’s one way to achieve that:

    def setUp(self):

        # Patch the close method of the two stream, so we can check they are closed correctly

        self.mock_close_input_stream = Mock()
        self.patch_input_stream_close_method = patch.object(self.mock_input_file, "close", self.mock_close_input_stream)
        self.patch_input_stream_close_method.start()

        self.mock_close_output_stream = Mock()
        self.patch_output_stream_close_method = patch.object(self.mock_output_file, "close", self.mock_close_output_stream)
        self.patch_output_stream_close_method.start()

    def tearDown(self):

        self.mock_open_method.stop()
        self.patch_input_stream_close_method.stop()

Making assertions in the tearDown

When you’ve written a few tests which duplicate an assertion, it can be cleaner to put this in the tearDown. This can be especially useful for stuff like resource de-allocation, or ensuring that files handles are closed etc.

    def tearDown(self):

        # Test that the config files are always opened as expected

        self.mock_open.assert_has_calls([call("/test/file.config", "r"), call("/test/file.config.new", "w")])

        # Test the config files are always closed

        self.mock_close_input_stream.assert_called_once()
        self.mock_close_output_stream.assert_called_once()

        # Test that the temp file is always cleaned up

        mock_os.remove.assert_called_with("/test/file.config.new")