Testing code is paramount, it ensures your code behaves as expected.
If you write Python unit tests, you’re likely familiar with the
MagicMock class, which enables the simulation of object behaviours for testing purposes.
One common testing scenario is checking how your code handles exceptions.
But how can you simulate an exception being raised from a mock object (like a mocked function)?
How can you leverage
MagicMock to raise exceptions deliberately, mimicking error scenarios during testing?
Whether you’re testing error handling, validating exception propagation, or ensuring that exceptions do not break your system,
MagicMock offers a solution.
In this article, we’ll learn how to raise exceptions in mock objects and demonstrate how Python’s
MagicMock provides a clean, efficient way to achieve this with one simple parameter.
We’ll also learn the difference between mocking and patching and how to test raised exceptions (mocked and non-mocked) with example code.
Are you ready?
After reading this article, you’ll learn
- How to mock functions in Pytest
- How to test exceptions in Pytest
- How to test exceptions raised by mocked functions in Pytest
- Master mocking and exception handling testing with Pytest
First a quick refresher of important concepts. Let’s get into it.
pytest refers to the practice of simulating specific behaviours of objects or functions to facilitate unit testing.
Instead of using actual system resources or making real external calls, mocking replaces these elements with
These mock objects can mimic any behaviour, such as returning specific values or raising exceptions, as instructed by the tester.
pytest in combination with packages like
unittest.mock, you can easily replace parts of your system with mock objects.
This ensures tests run faster, are isolated from external dependencies, and consistently produce predictable outcomes, making the testing process more efficient and reliable.
If you want to learn more about mocking, here’s a good starting point.
An important note about mocking
Mock an item where it is used, not where it came from.
This means you should always mock the function where it’s used rather than where it’s defined. We’ll see how to do this in the example code below.
Have you come across the words
Monkeypatch and have no idea what they mean?
Let’s start from the basics.
unittest.mock module, two prominent classes used for creating mock objects are
At a glance, they might seem redundant, but each has its distinct purpose in the realm of testing.
Mock class offers a basic foundation, allowing you to craft a mock object that can imitate virtually any other object.
It can also simulate method calls, and attribute access, and be configured to return specific values or raise exceptions, all while tracking how it’s interacted with.
MagicMock, on the other hand, is an enriched subclass of
Beyond inheriting all capabilities of
Mock, it predefines most of Python’s “magic” or “dunder” methods (like
__iter__, and more).
MagicMock especially adept at mimicking Python’s built-in types, like lists or dictionaries, and handling operations like iteration or item assignment.
In a nutshell, while
Mock provides essential mocking functionalities,
MagicMock elevates them to seamlessly emulate more intricate Python behaviours.
Maybe you’re wondering - does the
pytest-mock plugin use the
pytest-mock plugin is built on top of the
When you use the
mocker.patch() method provided by
pytest-mock, the default behaviour is to replace the specified object or method with a
So, by default,
However, it’s worth noting that you can specify which kind of mock object you’d like to use by providing the
new_callable argument to
For example, if you specifically want to use a basic
Mock instead of a
MagicMock, you can do so with the following:
But, without specifying this,
pytest-mock will default to using
Exception handling in Python provides a mechanism to gracefully respond to unexpected events, or “exceptions,” that occur during code execution.
Instead of allowing the program to crash or halt abruptly, Python’s exception-handling system centred around the
finally blocks, lets you anticipate potential issues and define alternative courses of action.
When code inside a
try block raises an exception, control shifts to the corresponding
except block where corrective measures or error messages can be implemented.
else block executes if no exceptions occur, while
finally ensures specific code runs regardless of an exception’s presence.
This robust framework ensures programs remain resilient and user-friendly, even in the face of unforeseen errors.
Now that we’ve nailed the theory let’s look at the example code to understand the concept.
To achieve the above objectives, the following is recommended:
- Basic knowledge of Python and Pytest (must have)
- Basics of Mocking (nice to have)
Here’s our repo structure
To get started, clone the repo here, or you can create your own repo by creating a folder and running
git init to initialise it.
In this project, we’ll be using Python 3.11.5.
pip install -r requirements.txt
The most important packages being
pytest , and
pytest-mock (to allow us to use
Let’s start with a very simple module containing a few file handling functions.
Creates and returns the path to a temporary directory.
def add_file(directory, filename, content):
Adds a file with the specified filename and content to the provided directory.
- directory (str): The path to the directory.
- filename (str): The name of the file to be created.
- content (str): The content to be written to the file.
- str: The path to the created file.
file_path = os.path.join(directory, filename)
with open(file_path, "w") as f:
Removes the specified file.
- filepath (str): The path to the file to be removed.
- bool: True if the file was removed successfully, False otherwise.
Removes the specified directory and all its contents.
- directory (str): The path to the directory to be removed.
- bool: True if the directory was removed successfully, False otherwise.
We have 4 functions that perform operations on files — add file, delete file and so on.
You’ll also notice the
FileNotFoundError exception being raised in the case of a missing file or directory.
We used the tempfile directory to create temporary directories and avoid leaving orphaned resources on disk.
Let’s go ahead and write a few unit tests. We’ll write one or two for each function.
from src.file_handler import (
Test that a temporary directory is created.
directory = create_temp_directory()
assert directory == "/tmp/test"
Test that a file is added to a directory.
filepath = add_file("/tmp", "test.txt", "test")
assert filepath == "/tmp/test.txt"
In this code snippet, we’re testing the creation of the tmp directory and adding a file.
Now we don’t really want to add files so we used the
mocker.patch method to mock the
These are the key methods that perform the operations in the source code, so mocking these will give us control over their outcomes.
Returning a value of
/tmp/test for the 1st function and allowing file creation for the 2nd function.
We can do the same with other methods as evident in the source code.
Exceptions can easily be tested in Pytest using the
with pytest.raises context manager.
We covered testing exception handling in great depth in our article on Pytest assert exceptions.
Test that a FileNotFoundError is raised when attempting to remove a file that does not exist. No mocking.
The above piece of code tests that a
FileNotFoundError is raised when you try to remove a file that doesn’t exist. Which is in line with our expectations.
In the previous section, you learned how to test raised exceptions in Pytest.
Now what about doing the same for mocked functions?
Why would this be useful?
Well, imagine you’re testing connectivity to a Postgres database and you want to simulate a
RequestTimeout Error or Connection Failure.
Or maybe testing a
FileNotFoundError when dealing with local files and you don’t necessarily want to create files or grant permissions.
Mocking helps you simulate all of these various use cases.
Test that a FileNotFoundError is raised when attempting to remove a file that does not exist.
Take a close look at this piece of code.
Here we’ve mocked the
os.remove method and used the
side_effect parameter to raise a
This bit is key.
If you read through the
unittest.mock docs, the
side_effect parameter mentions
This can either be a function to be called when the mock is called, an iterable or an exception (class or instance) to be raised.
This allows you to conveniently raise an exception among other things like returning an iterable (which is useful if you want your mock object to return multiple values).
Here’s another example
Test that a FileNotFoundError is raised when attempting to remove a directory that does not exist.
In this example, we mocked the
shutil.rmtree method and raised a similar exception using the
Finally let’s run all the tests to show this all works.
In the context of PyTest and unit testing in Python,
patch are tools for simulating or replacing parts of your code during testing.
- Mock: A
Mockis a standalone object used to simulate the behaviour of functions or objects. It allows you to set up specific behaviours for method calls, like returning values or raising exceptions.
from unittest.mock import Mock
mock_obj = Mock()
mock_obj.some_method.return_value = 42
result = mock_obj.some_method()
assert result == 42
patchis a context manager or decorator that temporarily replaces a function or object with a mock during a test. It’s particularly handy for mocking external dependencies.
from unittest.mock import patch
# Some external service call
mock_external.return_value = "Mocked data"
result = external_function()
assert result == "Mocked data"
Mock creates individual mock objects, while
patch temporarily replaces real objects/functions with mocks during tests, helping isolate your code and control its behaviour.
This article taught us some important concepts of mocking and raising exceptions.
First, we explored what Mocking is and how it differs from MagicMock.
Then we looked at the basics of exception handling and how to test an example
file_handler using mocking principles.
Lastly, we looked at how to test raised exceptions for mocked functions which is an important part of this article.
Mocking and patching are extremely important concepts in unit testing and your ability to master them will give you the freedom to test your code even with the most complex dependencies.
With this knowledge, you can now test exceptions raised by mocked functions with ease.
Till the next time… Cheers!