Have you ever found yourself in a situation where you needed to test a function that takes multiple arguments and returns something based on those arguments? Maybe it looks like this:
# This function takes in two arguments and returns a boolean value based on the conditions met.
def my_function(arg1, arg2):
# The function is named "my_function" and takes in two arguments, "arg1" and "arg2".
if arg1 == 'foo' and arg2 > 5:
# The "if" statement checks if "arg1" is equal to 'foo' and if "arg2" is greater than 5.
return True
# If the conditions are met, the function returns True.
else:
# If the conditions are not met, the function returns False.
return False
Now let’s say you want to test that function with a mock object. You might do something like this:
import unittest # Importing the unittest module to use for testing
from unittest.mock import Mock, patch # Importing the Mock and patch functions from the unittest.mock module
class TestMyFunction(unittest.TestCase): # Defining a test class for the function to be tested
def setUp(self): # Defining a setUp method to be executed before each test
self.mock = Mock() # Creating a mock object to use for testing
self.mock.return_value = True # Setting the return value of the mock object to True
@patch('my_module.some_function') # Using the patch function to mock the some_function from the my_module
def test_my_function(self, mock): # Defining a test method for the function to be tested
arg1 = 'foo' # Creating a variable to store the first argument for the function
arg2 = 7 # Creating a variable to store the second argument for the function
result = my_function(arg1, arg2) # Calling the function with the arguments and storing the result
self.assertEqual(result, True) # Asserting that the result is equal to True
mock.assert_called_with('some_argument') # Asserting that the mock object was called with the specified argument
This works fine for simple cases where you don’t care about the arguments that are passed to your mocked function. But what if you want to test a more complex scenario? Let’s say `my_function` takes an object as its first argument and calls some method on it with specific parameters:
# Define a function called my_function that takes in an object as its argument
def my_function(obj):
# Call the do_something method on the object, passing in the string 'foo' and the integer 5 as arguments
obj.do_something('foo', 5)
# Check if the object's some_property attribute is equal to the string 'bar'
if obj.some_property == 'bar':
# If it is, return True
return True
else:
# If it is not, return False
return False
Now you want to test that `my_function` is called correctly with the right arguments and returns the expected result based on some property of the object passed in. This is where copying arguments comes in handy!
Instead of creating a mock object for every argument, we can create a helper function that copies the original arguments and creates a new mock object with those copied arguments:
import unittest
from unittest.mock import Mock, patch, DEFAULT
# This function copies the arguments passed in and creates a new mock object with those copied arguments.
def copy_call_args(mock):
# This function will be used as the side effect for the mock object.
def side_effect(*args, **kwargs):
# Create a copy of the original arguments.
args = list(args)
# Loop through the arguments and check if they are dictionaries.
for i in range(len(args)):
if isinstance(args[i], dict):
# If they are dictionaries, create a deep copy to avoid modifying the original arguments.
args[i] = deepcopy(args[i])
# Create a copy of the original keyword arguments.
kwargs = dict(kwargs)
# Call the new mock object with the copied arguments and keyword arguments.
new_mock(*args, **kwargs)
# Return the default value.
return DEFAULT
# Set the side effect of the mock object to be the function we just defined.
mock.side_effect = side_effect
# Return the new mock object.
return Mock()
class TestMyFunction(unittest.TestCase):
def setUp(self):
# Create a mock object.
self.obj = Mock()
# Set the return value for the "do_something" method of the mock object.
self.obj.do_something.return_value = 'foo'
# Set the value for the "some_property" attribute of the mock object.
self.obj.some_property = 'bar'
# Use the patch decorator to mock the "some_function" function from the "my_module" module.
@patch('my_module.some_function')
def test_my_function(self, mock):
# Create a new mock object with copied arguments.
arg1 = copy_call_args(mock)
# Call the function we want to test, passing in the new mock object and the original mock object as arguments.
result = my_function(arg1(self.obj))
# Assert that the result is equal to True.
self.assertEqual(result, True)
# Assert that the "some_function" function was called with the correct argument.
mock.assert_called_with('some_argument')
In this example, we’re using the `copy_call_args()` function to create a new mock object with copied arguments for our first argument (which is an object). This allows us to test that `my_function` calls the correct method on the object with the expected parameters.
Give it a try and let me know if you have any questions or comments!