Unit tests are essential whenever we write robust code. They help us verify our application logic and inspect all other aspects of the code requirements; however, hurdles like complexity and external dependencies make it hard to write quality test cases.
At this point,
unittest.mock, a Python mock library, helps us overcome these obstacles. Today, we will learn about mock objects, their importance, and various example codes using Python
patch(), which will temporarily replace the target with the mock objects.
Mock Objects and Their Importance
Mock objects mimic the behavior of real objects in controlled ways, for instance, within the testing environment, where we perform different tests to ensure that code is working as expected. The process of using mock objects is called Mocking; it is a powerful tool to improve the quality of our tests.
Now, the point is, why do we have to go through mocking? What are the fundamental reasons for using it?
Various causes show the importance of using mocking in Python. Let’s have a look at them below:
- One primary reason is to improve the behavior of our code by using mock objects. For example, we can use them to test HTTP requests (a request made by a client to the named host, which locates on a server) and ensure whether they cause any intermittent failure or not.
- We can replace the actual HTTP requests with the mock objects, which allows us to fake an external service outage and completely successful responses in a predictable way.
- Mocking is also useful when it is difficult to test
exceptblocks. Using mock objects, we can control the execution flow of our code to reach such areas (
except) and improve code coverage.
- Another reason that increases the importance of using Python mock objects is properly understanding how we can use their counterparts in our code.
Patch() and Its Uses
unittest.mock, a mock object library in Python, has a
patch() that temporarily replaces the target with the mock object. Here, the target can be a class, a method, or a function.
To use the patch, we need to understand how to identify a target and call the
To recognize the target, make sure that the target is importable, then patch the target where it is utilized, not where it is coming from. We can call
patch() in three ways; as a decorator for a class/function, as a context manager, or as a manual start/stop.
The target is replaced with the new object when we use
patch() as a decorator of a class/function or use
patch() in the context manager inside a
with statement. In both scenarios, the patch is undone when the
with statement or the function exists.
Let’s create a startup code and save it in the
addition.py file, which we will import to use
patch() as a decorator, context manager, and manual start/stop.
Startup Example Code (saved in
def read_file(filename): with open(filename) as file: lines = file.readlines() return [float(line.strip()) for line in lines] def calculate_sum(filename): numbers = read_file(filename) return sum(numbers) filename = "./test.txt" calculate_sum(filename)
1 2 3
read_file() takes the
filename to read lines and converts every line into float type. It returns a list of these converted numbers, which we save in the
numbers variable inside the
calculate_sum() sums all the numbers in the
numbers list and returns it as an output as follows:
patch() as a Decorator
Let’s dive in step-by-step to learn the use of
patch() as a decorator. Complete source code is given at the end of all steps.
Import libraries and modules.
import unittest from unittest.mock import patch import addition
First, we import the
unittest.mockmodule. After that, we import
addition,our startup code.
@patch('addition.read_file') def test_calculate_sum(self, mock_read_file): #....
Next, we decorate the
test_calculate_sum()test method using
@patchdecorator. Here, the target is
read_file()function of the
test_calculate_sum()has an additional parameter
mock_read_filedue to using
@patchdecorator. This additional parameter is an instance of
MagicMock(we can rename
mock_read_fileto anything that we want).
Assign a list to
mock_read_file.return_value = [1, 2, 3]
calculate_sum()and test it.
result = addition.calculate_sum('') self.assertEqual(result, 6.0)
Now, we can call the
assertEqual()to test whether the sum is
Here, we can pass any
calculate_sum()function because the
mock_read_fileobject will be invoked instead of
Here is the complete source code.
Example Code (saved in
import unittest from unittest.mock import patch import addition class TestSum(unittest.TestCase): @patch('addition.read_file') def test_calculate_sum(self, mock_read_file): mock_read_file.return_value = [1, 2, 3] result = addition.calculate_sum('') self.assertEqual(result, 6.0)
Run a Test:
python -m unittest test_sum_patch_decorator -v
Run the test using the above command to get the following output.
test_calculate_sum (test_sum_patch_decorator.TestSum) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
patch() as the Context Manager
Example Code (saved in
import unittest from unittest.mock import patch import addition class TestSum(unittest.TestCase): def test_calculate_sum(self): with patch('addition.read_file') as mock_read_file: mock_read_file.return_value = [1, 2, 3] result = addition.calculate_sum('') self.assertEqual(result, 6)
This code is similar to the last code example where we used
patch() as the decorator, except for a few differences discussed here.
Now, we do not have
@patch('addition.read_file') line of code while the
self parameter (which is a default parameter) only.
with patch('addition.read_file') as mock_read_file means patch
mock_read_file object in the context manager.
In simple words, we can say that the
patch() will replace the
mock_read_file object within the
Now, run the test using the command below.
python -m unittest test_sum_patch_context_manager -v
test_calculate_sum (test_sum_patch_context_manager.TestSum) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
patch() to Manually Start/Stop
Example Code (saved in
import unittest from unittest.mock import patch import addition class TestSum(unittest.TestCase): def test_calculate_sum(self): patcher = patch('addition.read_file') mock_read_file = patcher.start() mock_read_file.return_value = [1, 2, 3] result = addition.calculate_sum('') self.assertEqual(result, 6.0) patcher.stop()
This code fence does the same as the previous two code examples, but here, we use
patch() manually. How? Let’s understand it below.
First, we import the required libraries. Inside the
test_calculate_sum(), we call
patch() to start a patch with the target
read_file() function of the
Then, we create one mock object for
read_file() function. After that, assign the list of numbers to
mock_read_file.return_value, call the
calculate_sum() and test the results using
Finally, we stop patching by calling the
stop() method. Now, run the following command to test.
python -m unittest test_sum_patch_manually -v
test_calculate_sum (test_sum_patch_manually.TestSum) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK