Mocking
pytest-mock — mocker fixture
The pytest-mock plugin provides a powerful 'mocker' fixture that simplifies object patching and state isolation within your test suite. It allows you to replace complex dependencies with controlled stubs, ensuring that your tests remain fast, deterministic, and decoupled from external side effects. Use this tool whenever your code interacts with databases, network APIs, or filesystem operations that are inappropriate or unreliable to execute during standard test cycles.
Introduction to Patching with Mocker
At its core, the 'mocker' fixture is a thin wrapper around the standard library's patching capabilities, specifically designed to handle the setup and teardown lifecycle automatically. When you patch an object using 'mocker.patch', the plugin tracks that modification and ensures that the original state of the object is restored immediately after the test completes. This is vital for maintaining test isolation, preventing side effects from leaking into subsequent test cases. Understanding that 'mocker' is effectively managing the 'unittest.mock' patcher simplifies the reasoning process: you are temporarily replacing a lookup path in a module with a mock object. Because Python modules import names into their namespace, you must patch the object where it is used, not where it is defined. This mechanism provides a robust way to intercept calls and inspect arguments, ensuring your logic behaves correctly without triggering actual heavy lifting.
# Example: Patching a simple function call
import my_app
def test_process_data(mocker):
# Patch 'calculate' in the 'my_app' namespace
mock_calc = mocker.patch('my_app.calculate', return_value=42)
result = my_app.process_data(10)
assert result == 42
# Verify that the function was called exactly once
mock_calc.assert_called_once_with(10)Managing Return Values and Exceptions
When isolating code, you often need to simulate scenarios that are difficult to trigger in real life, such as network timeouts or database connection failures. The 'mocker' fixture allows you to define these behaviors precisely using 'return_value' or 'side_effect'. By using 'side_effect', you can configure a mock to raise an exception when called, which allows you to test your error-handling logic and recovery paths without needing an actual failing system. The core principle here is control: by asserting that your application catches the specific exceptions you defined, you verify that your error handling is not just theoretical. This ensures that even under stress or failure conditions, your system preserves data integrity and reports errors correctly. Because 'mocker' takes care of resetting the state, each test starts with a clean slate, allowing you to chain multiple tests with different side effects without cross-contamination.
# Example: Triggering an exception
import requests
import my_app
def test_api_failure(mocker):
# Simulate a network timeout error
mocker.patch('requests.get', side_effect=TimeoutError('Connection failed'))
# Assert that our application handles the error gracefully
assert my_app.fetch_status() == 'System Unavailable'Patching Objects and Methods
Beyond simple functions, real-world applications rely heavily on class methods and objects. Patching a method within a class requires careful attention to the attribute path. When you use 'mocker.patch.object', you are targeting a specific instance or class rather than a string path in a module. This is particularly useful when you have an object already instantiated in your test scope. By targeting the object directly, you avoid issues with circular imports or complex namespace resolution. The 'mocker' fixture makes this intuitive: once the patch is applied, every call made to that method on the target instance is routed to your mock. This allows you to verify that internal state transitions occur correctly, or that specific method chains are triggered as expected. It is an essential technique for mocking internal service components while leaving the rest of the application object graph untouched, providing high-fidelity unit testing.
# Example: Patching a class method
class DataProcessor:
def save(self, data): return True
def test_save_method(mocker):
processor = DataProcessor()
# Patch the 'save' method on the instance
mocker.patch.object(processor, 'save', return_value=False)
assert processor.save("test") is FalseSpying on Existing Functions
Sometimes, you do not want to replace the behavior of a function, but merely observe how it is called. This is where 'mocker.spy' becomes an indispensable tool. A spy wraps an existing function, allowing it to execute its real logic while simultaneously recording every invocation, argument passed, and return value. This is powerful for verifying interactions between components in integration-style unit tests where the original logic is necessary for the test to proceed. By using a spy, you maintain the functional integrity of the code while gaining full visibility into the call history. This avoids the common trap of 'over-mocking', where tests become brittle because they describe how a function is called rather than what it achieves. Spying allows you to assert on behavior without sacrificing the real implementation, creating a bridge between simple unit tests and full-scale integration tests.
# Example: Using a spy to record calls
def real_worker(): return "done"
def test_spy_example(mocker):
# Create a spy on the existing function
spy = mocker.spy(locals(), 'real_worker')
res = real_worker()
assert res == "done"
assert spy.called # Verify it was executedAdvanced Mocking Patterns
As your test suite grows, you may encounter scenarios where you need to return different values on consecutive calls, or dynamically generate return values based on input arguments. 'mocker' supports this through advanced configurations of 'side_effect'. By passing an iterable to 'side_effect', you can simulate a stream of data that returns a new item every time the mock is called, eventually raising an 'StopIteration' or simply reusing the last value. Furthermore, you can pass a function to 'side_effect' that computes a return value based on the arguments received by the mock. This provides the ultimate flexibility, allowing your mocks to behave like lightweight, context-aware doubles. Mastering these patterns allows you to simulate complex state machines, retry logic, and pagination workflows. By leveraging these advanced features, you create tests that are both expressive and deeply representative of your application's actual runtime dynamics.
# Example: Dynamic side effects
def test_dynamic_behavior(mocker):
mock_func = mocker.Mock()
# Return values sequentially
mock_func.side_effect = [1, 2, 3]
assert mock_func() == 1
assert mock_func() == 2
assert mock_func() == 3Key points
- The mocker fixture automatically handles cleanup, ensuring that all patches are reverted after every test case.
- Always patch the object in the namespace where it is imported and used, rather than where it is originally defined.
- Use return_value to define static outputs for a mock, which is ideal for isolating pure logic branches.
- Utilize side_effect to simulate complex scenarios like raising exceptions or generating unique return values dynamically.
- The mocker.spy utility allows you to observe calls to real functions without overriding their underlying implementation.
- Use mocker.patch.object when you need to specifically target methods on a class instance rather than a module path.
- Over-mocking can lead to brittle tests; prefer spying or partial mocking when the real logic is safe and necessary.
- Dynamic side effects can mimic state-dependent behavior, allowing for robust testing of retry mechanisms and data streams.
Common mistakes
- Mistake: Manually calling 'patch.stopall()' in teardown. Why it's wrong: The mocker fixture automatically handles cleanup after each test. Fix: Rely on the fixture's built-in teardown mechanism to avoid leakage.
- Mistake: Mocking objects in the wrong namespace. Why it's wrong: Mocking an object where it is defined rather than where it is imported often leads to the mock not being applied. Fix: Always mock the object in the module where it is being used.
- Mistake: Forgetting to use 'autospec=True'. Why it's wrong: Without autospec, mocks will accept any arguments, hiding bugs where the real function signature has changed. Fix: Set 'autospec=True' to ensure the mock enforces the interface of the original object.
- Mistake: Over-mocking standard library or internal implementations. Why it's wrong: It creates fragile tests that break when internal code is refactored, even if external behavior stays the same. Fix: Mock only the boundaries where your code interacts with external dependencies.
- Mistake: Asserting side effects before calling the mocked function. Why it's wrong: Mocks track calls dynamically; asserting prematurely will lead to false negatives. Fix: Ensure assertions are placed after the code under test has been executed.
Interview questions
What is the primary purpose of the 'mocker' fixture provided by pytest-mock?
The 'mocker' fixture is a wrapper around the standard library's unittest.mock module that integrates seamlessly with the pytest framework. Its primary purpose is to provide a safe and convenient way to replace parts of your system under test with mock objects. By using 'mocker', you ensure that any mocks created are automatically unpatched when the test function completes, which prevents side effects from leaking into other tests and keeps your test suite reliable and isolated.
How does using the 'mocker' fixture differ from manually using 'unittest.mock.patch'?
When you use the standard library's 'patch', you are responsible for managing the lifecycle of the patch, which often involves using context managers or decorators that can make code difficult to read. In contrast, 'mocker' handles the setup and teardown automatically as part of the fixture's lifecycle. Because 'mocker' is a fixture, it follows pytest's dependency injection pattern, making your code cleaner and avoiding the 'patch-hell' that occurs when stacking multiple decorators on a single test function.
Can you explain how to use the 'mocker.patch' method to mock a return value for a function?
To mock a return value, you call 'mocker.patch' with the import path of the object you intend to replace. For example, if you are testing a service that calls an external API, you would write: 'mock_api = mocker.patch("my_app.service.get_data")'. After this call, you can set the return value using 'mock_api.return_value = {"key": "value"}'. Now, whenever your code executes 'get_data', it will receive your predefined dictionary instead of calling the actual, potentially slow or unreliable, external API.
How can you verify that a specific method was called with expected arguments using 'mocker'?
After you have mocked a function or method using 'mocker.patch', the resulting mock object keeps track of every call made to it. To verify interactions, you use assertion methods like 'assert_called_once_with' or 'assert_called_with'. For instance, if your function 'process_payment' is supposed to call 'stripe.charge(amount=100)', your test would involve checking 'mock_stripe_charge.assert_called_once_with(amount=100)'. This is crucial for verifying that your code logic correctly communicates with its dependencies without actually triggering those dependencies' side effects.
Compare the use of 'mocker.patch' against 'mocker.patch.object'—when would you choose one over the other?
The primary difference lies in how you reference the object. 'mocker.patch' takes a string path, such as 'package.module.ClassName', which is useful when the object is imported within the module you are testing. 'mocker.patch.object', however, takes the actual object instance or class as the first argument, followed by the string name of the attribute to mock. You should choose 'patch.object' when you already have the object imported or available in your test scope, as it avoids issues with string-based path resolution and is generally more resilient to refactoring code that might move classes between different modules.
How does 'mocker.spy' differ from 'mocker.patch', and in what scenario is it most beneficial?
While 'mocker.patch' completely replaces an object with a new mock, 'mocker.spy' wraps an existing function, allowing it to execute its real logic while still recording call history. Use a spy when you want to verify that a method was called correctly, but you still need the original code to perform its actual work. This is highly effective for testing internal utility functions where you want to ensure the function actually runs but still want to assert how many times or with what specific arguments it was invoked during execution.
Check yourself
1. Which of the following best describes the primary advantage of the 'mocker' fixture over the standard 'unittest.mock.patch' decorator?
- A.It provides a higher execution speed for the test suite.
- B.It automatically cleans up patches after the test finishes, reducing boilerplate code.
- C.It allows mocking of private methods that 'unittest' cannot reach.
- D.It prevents tests from failing due to race conditions.
Show answer
B. It automatically cleans up patches after the test finishes, reducing boilerplate code.
The mocker fixture manages the lifecycle of patches automatically via pytest's teardown process. Option 0 is false as performance is similar. Option 2 is false as the underlying patch logic is identical. Option 3 is false as race conditions are outside the scope of mocking.
2. You have a module 'app.services' that uses 'requests.get'. Where should you place the mocker path for testing?
- A.mocker.patch('requests.get')
- B.mocker.patch('app.services.requests.get')
- C.mocker.patch('requests.api.get')
- D.mocker.patch('app.services.requests.api.get')
Show answer
B. mocker.patch('app.services.requests.get')
In Python, you must mock the object where it is imported and used, not where it is defined. Option 0 and 2 fail because they mock the origin. Option 3 is an incorrect pathing. Option 1 correctly targets the lookup location in the service.
3. What is the primary benefit of using 'autospec=True' when creating a mock?
- A.It allows the mock to run as fast as compiled code.
- B.It automatically records all calls made to the function.
- C.It creates a mock object that has the same attributes and methods as the object being replaced.
- D.It ensures the mock returns the same value as the real function.
Show answer
C. It creates a mock object that has the same attributes and methods as the object being replaced.
Autospec verifies the mock object matches the interface of the target, catching signature mismatches. Option 0 is nonsense. Option 1 is standard mock behavior. Option 3 is incorrect as the mock does not execute the original logic by default.
4. When testing a function that performs a network request, why might you prefer 'mocker.patch' over creating a manual Mock class?
- A.It is mandatory for all network tests in pytest.
- B.It allows for easier assertion of how many times the dependency was called and with what arguments.
- C.It automatically handles JSON serialization of the arguments.
- D.It prevents the need for writing test assertions entirely.
Show answer
B. It allows for easier assertion of how many times the dependency was called and with what arguments.
Mocker makes inspection of call counts and arguments trivial using assert_called_with. Option 0 is false; it is a convenience. Option 2 is false. Option 3 is false; you still must assert outcomes.
5. If you need to verify that a mock was called twice with specific arguments, which method should you use?
- A.mock_obj.assert_called_twice_with(args)
- B.mock_obj.assert_has_calls([call(args), call(args)])
- C.mock_obj.verify_calls(2, args)
- D.mock_obj.count_calls(2)
Show answer
B. mock_obj.assert_has_calls([call(args), call(args)])
assert_has_calls allows verifying an ordered sequence of calls. Option 0 does not exist in standard unittest.mock. Option 2 and 3 are not valid API methods.