How to Mock Multiple Return Values in Python Unit Tests
When unit testing, you often need to mock functions or methods to return different values on successive calls.
This guide explains how to mock multiple return values using the unittest.mock
module in Python, covering the side_effect
attribute of mock objects and the patch()
decorator, as well as ways to handle the end of a sequence of return values.
Using side_effect
with a List
The most direct way to mock multiple return values is to set the side_effect
attribute of a Mock
object to a list:
from unittest.mock import Mock
m = Mock()
m.side_effect = ['tutorial', 'reference', 'com']
print(m()) # Output: tutorial
print(m()) # Output: reference
print(m()) # Output: com
- Each time the mock object (
m
in this case) is called, it returns the next value from theside_effect
list.
Handling StopIteration
Once the side_effect
list is exhausted, calling the mock again will raise a StopIteration
exception:
from unittest.mock import Mock
m = Mock()
m.side_effect = ['tutorial', 'reference', 'com']
print(m()) # Output: tutorial
print(m()) # Output: reference
print(m()) # Output: com
try:
print(m()) # Raises StopIteration
except StopIteration:
print("StopIteration raised!")
- You can catch this exception using
try/except
, or you may want to provide a default after the list of return values are exhausted.
Using itertools.chain
for default after exhaustion
from unittest.mock import Mock
import itertools
m = Mock()
m.side_effect = itertools.chain(
['tutorial', 'reference'],
itertools.repeat('com') # Repeats "com" forever
)
print(m()) # Output: tutorial
print(m()) # Output: reference
print(m()) # Output: com
print(m()) # Output: com
- The
itertools.chain()
method can be used to chain iterators. - The
itertools.repeat()
method will create an iterator that returns a constant value.
Using side_effect
with a List (upon instantiation)
You can also define the return values during the creation of the Mock
object.
from unittest.mock import Mock
m = Mock(side_effect=['tutorial', 'reference', 'com'])
print(m()) # Output: tutorial
print(m()) # Output: reference
print(m()) # Output: com
Using side_effect
with MagicMock
If your mocked object needs to behave like a real object (with dunder methods defined), then you can use the MagicMock
class instead.
from unittest.mock import MagicMock
m = MagicMock(side_effect=['tutorial', 'reference', 'com'])
print(m()) # Output: tutorial
print(m()) # Output: reference
print(m()) # Output: com
MagicMock
is a subclass ofMock
that implements default behaviors for magic methods.
Using patch()
with side_effect
The patch()
decorator (or context manager) is commonly used to replace a function or method with a mock during a test. You can combine patch()
with side_effect
to control the return values:
def greet(name):
return f'hello {name}'
from unittest.mock import patch
import example # Import the module containing the function
@patch('example.greet', side_effect=['hello Alice', 'hello Anna', 'hello Carl'])
def test_greet_function(mock_greet): # The mock is passed as an argument
assert example.greet('Alice') == 'hello Alice'
mock_greet.assert_called_with('Alice')
assert example.greet('Anna') == 'hello Anna'
mock_greet.assert_called_with('Anna')
assert example.greet('Carl') == 'hello Carl'
mock_greet.assert_called_with('Carl')
print(mock_greet)
print(mock_greet.called)
assert mock_greet.call_count == 3
test_greet_function()
@patch('example.greet', ...)
: This decorator replaces thegreet
function within theexample
module with aMock
object. The important thing is that you're patching where it's used, not where it's defined.side_effect=['hello Alice', ...]
: This sets theside_effect
of the mock to the provided list.def test_greet_function(mock_greet):
: The mock object is passed as an argument (mock_greet
in this case) to the test function.example.greet(...)
: Inside the test, you call the original function as if it were not mocked. The mocking happens behind the scenes because of thepatch
decorator.- You can use
mock_greet.assert_called_with()
to make assertions about how the mocked function was called.