Python > Testing in Python > Mocking and Patching > Using the `unittest.mock` module

Mocking a Class with `unittest.mock.patch.object`

This snippet demonstrates how to use unittest.mock.patch.object to replace a method of a class with a mock during testing. This technique is especially useful when you want to test the interaction of a class with one of its own methods or when the class is passed as an argument.

Code Snippet

This code defines a class ProductionClass with a method method_to_mock which is meant to represent a complicated task. another_method calls method_to_mock. function_under_test takes an instance of ProductionClass as an argument and calls another_method on it.

The test case TestProductionClass uses @patch.object(ProductionClass, 'method_to_mock') to replace method_to_mock with a mock object. We set the return_value of the mock and then call function_under_test. Finally, assertions check if the returned result is correct and verify if the mock was called with the appropriate argument.

import unittest
from unittest.mock import patch

class ProductionClass:
    def method_to_mock(self, arg):
        # Assume this method performs a complex calculation or interacts
        # with external resources.
        return f"Real result with {arg}"

    def another_method(self):
        return self.method_to_mock("default")


def function_under_test(obj):
    return obj.another_method()

class TestProductionClass(unittest.TestCase):

    @patch.object(ProductionClass, 'method_to_mock')
    def test_function_under_test(self, mock_method):
        mock_method.return_value = "Mocked result"

        instance = ProductionClass()
        result = function_under_test(instance)

        self.assertEqual(result, "Mocked result")
        mock_method.assert_called_with("default") #verify the arguments passed


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

Concepts Behind the Snippet

Patching Objects: patch.object is used to replace specific attributes (methods in this case) of a given object with a mock. This is useful when you want to control the behavior of a particular method without affecting other parts of the class or its instances.

Context Management: patch.object returns a context manager, allowing the mock to be active only within a specific code block (e.g., the test method). It automatically restores the original method after the block is executed.

Real-Life Use Case

Consider a class that interacts with a file system or a network resource. You can mock the methods responsible for these interactions to test other functionalities of the class without actually reading from or writing to files or making network requests. This ensures that the test remains fast, reliable, and avoids potential side effects.

Best Practices

  • Target specific methods: Use patch.object to mock only the methods that are relevant to the test. This avoids unnecessary mocking and reduces the risk of breaking other parts of the code.
  • Verify method calls: Use assert_called, assert_called_with, and other assertion methods to ensure that the mocked method is called with the expected arguments.
  • Restore the original object: Always ensure that the original object is restored after the test, either by using the context manager or calling stop() on the patcher.

Interview Tip

Be ready to explain the advantages of using patch.object over other mocking techniques, especially when testing class methods. Highlight its ability to target specific methods and its ease of use with context managers.

When to Use `patch.object`

Use patch.object when:

  • You want to mock a method of a specific object.
  • You need to test the interaction of a class with one of its own methods.
  • You want to mock a method within a specific scope.

Pros

  • Targeted mocking: Allows you to mock specific methods of an object without affecting other parts of the code.
  • Easy to use: Simplifies the process of mocking methods with context managers.
  • Automatic restoration: Restores the original object after the test.

Cons

  • Can be verbose: May require more code than other mocking techniques for simple cases.
  • Requires knowledge of the object: Needs to know the class and attribute to be patched.

FAQ

  • What happens if the patched object/method doesn't exist?

    If the object or method specified in patch.object doesn't exist, the patcher will raise an AttributeError.
  • How do I mock multiple methods of the same class?

    You can use multiple @patch.object decorators, one for each method you want to mock.