Python > Quality and Best Practices > Testing > Unit Testing with `unittest`
Advanced Unit Testing: Mocking with `unittest.mock`
This snippet demonstrates the use of mocking in unit tests using Python's `unittest.mock` module. Mocking allows you to replace dependencies of the code under test with controlled substitutes, enabling you to isolate and test specific units of code in a predictable environment.
Concepts Behind the Snippet
Mocking is a crucial technique when unit testing code that interacts with external resources, such as databases, APIs, or filesystems. By replacing these dependencies with mock objects, you can avoid real-world side effects and create deterministic test conditions. This allows you to test the logic of your code independently of the behavior of its dependencies.
Example Code
This code defines an `ExternalService` that represents an external dependency. `MyClass` uses this service to process data. The `TestMyClass` uses `@patch` to replace `ExternalService` with a mock object. We configure the mock object to return a specific value when `get_data` is called. This allows us to test the `process_data` method without actually connecting to an external service.
import unittest
from unittest.mock import patch
class ExternalService:
def get_data(self):
# Simulates fetching data from an external source
raise NotImplementedError("Should be implemented")
class MyClass:
def __init__(self, service):
self.service = service
def process_data(self):
data = self.service.get_data()
return data.upper()
class TestMyClass(unittest.TestCase):
@patch('__main__.ExternalService')
def test_process_data(self, MockExternalService):
# Configure the mock
mock_service = MockExternalService.return_value
mock_service.get_data.return_value = "some data"
# Instantiate the class with the mock service
my_instance = MyClass(mock_service)
# Call the method under test
result = my_instance.process_data()
# Assert the result
self.assertEqual(result, "SOME DATA")
# Assert that the mock was called
mock_service.get_data.assert_called_once()
if __name__ == '__main__':
unittest.main()
Explanation of the Code
Real-Life Use Case
Consider a function that sends an email. In a unit test, you wouldn't want to actually send an email every time the test runs. Instead, you would mock the email sending function to verify that it's called with the correct parameters, without actually sending the email.
Best Practices
Interview Tip
Be prepared to explain the purpose of mocking, the different ways to create mock objects, and how to configure their behavior. You should also be able to discuss the benefits and drawbacks of mocking.
When to Use Them
Use mocking when you need to isolate a unit of code from its dependencies, when you want to avoid real-world side effects, or when you need to create deterministic test conditions.
Memory Footprint
The memory footprint of mock objects is generally small. However, if you are creating a large number of mocks or if your mock objects contain large data structures, it's important to be mindful of memory usage.
Alternatives
Other mocking libraries available in Python include `mock` (the older version of `unittest.mock`, now deprecated), `doublex`, and others. However, `unittest.mock` is the standard and generally preferred option.
Pros
Cons
FAQ
-
What is the difference between `patch` and `Mock`?
`patch` is a decorator or context manager that replaces an object with a mock object for the duration of the test. `Mock` is a class that creates a mock object. `patch` often uses `Mock` internally. -
How do I verify that a mock was called with specific arguments?
Use the `assert_called_with` method to verify that the mock was called with specific arguments. For example: `mock_service.get_data.assert_called_with(arg1, arg2)`. -
Can I mock multiple objects in a single test?
Yes, you can use multiple `@patch` decorators or context managers to mock multiple objects in a single test.