Python tutorials > Testing > Mocking > How to patch objects/functions?

How to patch objects/functions?

Patching is a powerful technique in Python's testing framework, particularly when using the unittest.mock module. It allows you to replace objects or functions in your code with mock objects during tests. This isolates the code under test and enables you to control the behavior of dependencies, making your tests more predictable and reliable. This tutorial explores how to use unittest.mock.patch effectively to mock objects and functions in your Python tests.

Basic Patching Example

This example demonstrates a basic use case of unittest.mock.patch. We have a function get_data_from_api that, in a real-world scenario, would retrieve data from an external API. We also have process_data which uses this data. In our test, we use @patch('__main__.get_data_from_api') to replace the actual get_data_from_api function with a mock object. The mock object is then passed as an argument to the test method (test_process_data_with_mock). We can then configure the mock object's return value using mock_get_data_from_api.return_value = "Mocked API Data". This allows us to control what the process_data function receives as input, isolating it from the external API dependency. Finally, we assert that the result of process_data is what we expect, given the mocked data.

Important: Note that '__main__.get_data_from_api' is the fully qualified name of the function within the module where the test is run. If your functions are defined in a separate module, you will need to adjust the patch target accordingly (e.g., 'my_module.get_data_from_api').

import unittest
from unittest.mock import patch

# Suppose this is in your module 'my_module.py'
def get_data_from_api():
    # Normally, this would call an external API
    return "Real API Data"


def process_data():
    data = get_data_from_api()
    return f"Processed: {data}"


class TestProcessData(unittest.TestCase):

    @patch('__main__.get_data_from_api')  # Replace '__main__.get_data_from_api'
    def test_process_data_with_mock(self, mock_get_data_from_api):
        # Configure the mock object's return value
        mock_get_data_from_api.return_value = "Mocked API Data"

        # Call the function that uses the patched function
        result = process_data()

        # Assert that the result is what you expect given the mocked return value
        self.assertEqual(result, "Processed: Mocked API Data")

        # Optionally, assert that the mock was called
        mock_get_data_from_api.assert_called_once()

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

Concepts Behind the Snippet

Several key concepts are demonstrated in the previous snippet:

  • Mocking: Replacing real dependencies with controlled substitutes (mock objects).
  • Patching: Dynamically replacing objects or functions with mock objects using unittest.mock.patch.
  • Isolation: Isolating the code under test by controlling its dependencies.
  • Assertion: Verifying that the code under test behaves as expected, given the mocked dependencies.
  • Target Specification: Ensuring you are patching the correct object. The target string passed to patch needs to be the location where the object is looked up, not where it's defined. This is a common source of confusion.

Real-Life Use Case

Imagine you're testing a function that interacts with a database. Directly interacting with the database during testing can be slow, unreliable (due to network issues, database availability), and can modify your data. Patching the database connection object (or a function that executes queries) allows you to simulate database interactions without actually touching the database.

Another common use case is when dealing with external APIs, as shown in the example above. You don't want your tests to depend on the availability or performance of external services. Patching the function that calls the API allows you to simulate different API responses, including error scenarios.

Patching Attributes of Objects

In this example, we use patch.object to patch the value attribute of the MyClass class. We specify the class (MyClass), the attribute to patch ('value'), and the new value to assign to the attribute (new=20). During the test, the value attribute of MyClass is temporarily replaced with 20. This allows us to control the behavior of the MyClass instance and test the use_my_class function in isolation.

import unittest
from unittest.mock import patch

class MyClass:
    def __init__(self):
        self.value = 10

    def get_value(self):
        return self.value

def use_my_class(obj):
    return obj.get_value() + 5

class TestUseMyClass(unittest.TestCase):

    @patch.object(MyClass, 'value', new=20)  # Patch the 'value' attribute of MyClass
    def test_use_my_class_with_patched_attribute(self):
        obj = MyClass()
        result = use_my_class(obj)
        self.assertEqual(result, 25)

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

Patching with with Statement

The with statement provides a convenient way to apply and automatically revert patches. In this example, we use patch.dict to patch the os.environ dictionary. Within the with block, the MY_VARIABLE environment variable is set to 'patched_value'. After the with block exits, the os.environ dictionary is automatically restored to its original state.

This approach ensures that patches are always reverted, even if exceptions occur within the with block, preventing unintended side effects on other tests or code execution.

import unittest
from unittest.mock import patch

def my_function():
    import os
    return os.environ.get('MY_VARIABLE', 'default_value')

class TestMyFunction(unittest.TestCase):

    def test_my_function_with_patched_env_variable(self):
        with patch.dict('os.environ', {'MY_VARIABLE': 'patched_value'}):
            result = my_function()
            self.assertEqual(result, 'patched_value')

        # After the 'with' block, os.environ is restored to its original state
        # result = my_function()  # This would now return 'default_value'

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

Best Practices

  • Be Specific with Patch Targets: Use the correct fully qualified name of the object or function you want to patch. Double-check that the target points to where the object is looked up, not where it's defined.
  • Keep Patches Local: Use the with statement or the function decorator approach with patch.stopall() (if you must globally patch) to ensure patches are reverted after the test is complete.
  • Test Interactions, Not Implementations: Focus on verifying that functions call the correct dependencies with the correct arguments, rather than testing the internal logic of those dependencies (which should have their own tests).
  • Verify Mock Calls: Use mock.assert_called_once(), mock.assert_called_with(), and other assertion methods to verify that mocks were called as expected.
  • Consider Dependency Injection: For larger projects, consider using dependency injection to make your code more testable and reduce the need for patching. Dependency injection involves passing dependencies explicitly as arguments to functions or classes, rather than hardcoding them within the function or class. This allows you to easily substitute mock dependencies during testing.

Interview Tip

When discussing patching in interviews, emphasize your understanding of its purpose: isolating code for more reliable and controlled testing. Explain how patching allows you to simulate different scenarios, including error conditions, without relying on external systems. Be prepared to discuss the challenges of patching, such as identifying the correct patch target, and the importance of reverting patches to avoid side effects.

When to Use Patching

Use patching when you need to:

  • Isolate the code under test from its dependencies.
  • Simulate different scenarios, including error conditions and edge cases.
  • Control the behavior of external systems, such as databases, APIs, and file systems.
  • Avoid modifying real data during testing.
  • Speed up tests by replacing slow or unreliable dependencies with mock objects.

Memory Footprint

Patching itself doesn't have a significant memory footprint. The mock objects created by unittest.mock are relatively lightweight. However, if you create a large number of mocks or store a lot of data within the mock objects (e.g., using side_effect to return large datasets), the memory usage can increase.

Alternatives

While patching is a powerful technique, there are alternatives to consider:

  • Dependency Injection: Make dependencies explicit by passing them as arguments to functions or classes. This often simplifies testing by avoiding the need for patching.
  • Test Doubles (Stubs, Spies): Create simplified versions of dependencies for testing purposes. While similar to mocks, stubs typically provide predefined responses, while spies record how they were used.
  • Integration Tests: In some cases, it may be necessary to perform integration tests that interact with real dependencies. However, these tests should be carefully designed to minimize side effects and should not replace unit tests that focus on isolating individual components.

Pros

  • Isolation: Isolates the code under test, making tests more reliable and predictable.
  • Control: Allows you to control the behavior of dependencies, simulating different scenarios.
  • Speed: Can speed up tests by replacing slow dependencies with mock objects.
  • Flexibility: Provides a flexible way to mock objects and functions in various ways.

Cons

  • Complexity: Can be complex to set up and understand, especially for complex patching scenarios.
  • Fragility: Tests can become fragile if they are too tightly coupled to the implementation details of the mocked dependencies.
  • Over-Mocking: It's possible to over-mock, leading to tests that don't accurately reflect the real-world behavior of the code.
  • Potential for Incorrect Patch Target: Can be tricky to determine the correct patch target, leading to tests that don't mock the intended object or function.

FAQ

  • Why is my patch not working?

    The most common reason is an incorrect patch target. Double-check that you are patching the object where it is looked up, not where it is defined. Also, make sure the module path is correct.

  • How can I verify that my mock was called with specific arguments?

    Use the mock.assert_called_with(*args, **kwargs) method to verify that the mock was called with the expected arguments.

  • How do I mock a method that is called multiple times with different arguments?

    You can use the side_effect attribute of the mock object to specify a list of return values or a function that will be called each time the mock is invoked. The function can then return different values based on the arguments passed to it.

  • Can I mock built-in functions like open() or time.sleep()?

    Yes, you can mock built-in functions using unittest.mock.patch. Just specify the correct fully qualified name of the built-in function (e.g., '__builtin__.open' for Python 2 or 'builtins.open' for Python 3).