Python Mock Raise Exception

Python Mock Raise Exception

  1. Throw an Exception When Using the Unit-Testing Library unittest in Python
  2. Manually Raise an Exception From a Function

The main aim of this article is to demonstrate how to throw an exception when using the unit testing library unittest.

Throw an Exception When Using the Unit-Testing Library unittest in Python

To verify that our code is stable and accounts for most of the use cases in the project’s scope, we must test it by giving it varied inputs. One of the libraries present in Python, unittest, offers such functionality, allowing one to write and carry out different test cases.

While testing, it is necessary to be able to handle cases where the input is unexpected. We can handle such cases by throwing an exception detailing what is wrong with the given input.

Consider the following code:

class A:
    def __init__(self):
        pass

    def computeData(self):
        return 0

class B:
    def method_to_test():
        obj = A()
        try:
            print(obj.computeData())
        except Exception as e:
            print("Exception at method_to_test: " + str(e))

Let’s say that the function we want to test is method_to_test. It initializes the object of another class A and then calls one of its methods named computeData in a try-except block to catch any exceptions.

If we want to test the behavior of computeData when a specific exception is thrown, we have to do so using the unittest module.

Raise Exception Once

Considering the scenario mentioned above, it can also be the case that we want to have more control over when to throw an exception.

There can be plenty of reasons why such a need can arise. Depending on the implementation of your code, having more control over the behavior can be pretty helpful.

Assert Exception

While writing test cases, you may want to check whether your exceptions trigger or not. This is especially useful when you want to know if an exception was thrown or not.

This can help you spot earlier in your test cases if something goes wrong.

Mock Third-Party Modules

Third-party modules, depending on our implementation, can range from almost none to a lot. To test whether a third-party module behaves as intended, we must also test its behavior with varied outputs.

One such example would be request, a widely used module when communicating over a network or sending/receiving data using HTTP.

Manually Raise an Exception From a Function

To manually throw an exception from the computeData function, we have to use the side_effect attribute.

Consider the following code:

import unittest
from unittest.mock import patch
from unittest.mock import MagicMock

class A:
    def __init__(self) -> None:
        pass

    def computeData(self):
        return 0

class B:
    def method_to_test():
        obj = A()
        try:
            print(obj.computeData())
        except Exception as e:
            print("Exception at method_to_test: " + str(e))

class Test(unittest.TestCase):
    @patch.object(A, 'computeData', MagicMock(side_effect=Exception("Test")))
    def test_method(self):
        B.method_to_test()

if __name__ == '__main__':
    Test().test_method()

Output:

Exception at method_to_test: Test

In the solution, a new method, test_method, is created to test the method method_to_test. It is also important to note that the function is decorated with patch.object.

The patch decorator is provided in the module for patching modules and class-level attributes. To be more specific on what to patch, we use the patch.object instead of patch to patch the method directly.

In the decorator, the class name A is passed. This indicates the object to be patched is a part of A with the member name computeData being passed.

The last parameter is a MagicMock object, a subclass of Mock, where we associate our required behavior to computeData using the side_effect attribute, which is to throw an exception in our case.

The general flow of the program is as follows:

  1. The test_method is called.
  2. The computeData of class A is patched. A side_effect is assigned, which in our case, is an exception.
  3. The method_to_test of class B is called.
  4. The constructor of class A is called, with the instance stored in obj.
  5. The computeData is called. Due to the method being patched, an exception is thrown.

Raise an Exception Once

To only raise the exception once or have more control over the function’s behavior, side_effect supports more than just a function call.

Currently, side_effect supports:

  1. Iterable
  2. Callable
  3. Exception (Instance or Class)

Using Iterable, we can make the method only raise an exception once.

class Test(unittest.TestCase):
    @patch.object(A, 'computeData', MagicMock(side_effect=[1,Exception("Test"),3]))
    def test_method(self):
        B.method_to_test()

if __name__ == '__main__':
    testClass = Test()
    testClass.test_method()
    testClass.test_method()
    testClass.test_method()

Output:

1
Exception at method_to_test: Test
3

Due to the passed Iterable, elements of our choice are patched, and we gain control over when to raise exceptions. Hence, the exception is only thrown the second time the function is called, and 1 and 3 are returned in other cases.

Assert Exception

To determine if a specific exception occurred or not, we can use unittest.TestCase.assertRaises in case our desired exception is not thrown.

class Test(unittest.TestCase):
    @patch.object(A, 'computeData', MagicMock(side_effect=Exception("Test")))
    def test_method(self):
        self.assertRaises(KeyError, A.computeData)

if __name__ == '__main__':
    Test().test_method()

Output:

Exception at method_to_test: Test
Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 28, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 25, in test_method
    self.assertRaises(KeyError, A.computeData)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 738, in assertRaises
    return context.handle('assertRaises', args, kwargs)
  File "C:\Program Files\Python310\lib\unittest\case.py", line 201, in handle
    callable_obj(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1104, in __call__
    return self._mock_call(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1108, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1163, in _execute_mock_call
    raise effect
  File "d:\Python Articles\a.py", line 17, in method_to_test
Exception: Test

We are making minor changes to our test_method method, calling assertRaises with two parameters, which resulted in the above output.

The two parameters can be defined as:

  1. KeyError - Any Exception you want to trigger is passed here.
  2. A.computeData - The method which is supposed to throw the exception.

Mock Third-Party Modules

To mock a post, like before, you must use the patch decorator. A simple example can be as follows:

class Test(unittest.TestCase):
    @patch('requests.post', MagicMock(side_effect=requests.exceptions.ConnectionError()))
    def test_method(self):
        requests.post()

Output:

Traceback (most recent call last):
  File "d:\Python Articles\a.py", line 33, in <module>
    Test().test_method()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1369, in patched
    return func(*newargs, **newkeywargs)
  File "d:\Python Articles\a.py", line 30, in test_method
    requests.post()
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1104, in __call__
    return self._mock_call(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1108, in _mock_call
    return self._execute_mock_call(*args, **kwargs)
  File "C:\Program Files\Python310\lib\unittest\mock.py", line 1163, in _execute_mock_call
    raise effect
requests.exceptions.ConnectionError

The functions from third-party modules can also be patched using the patch decorator, allowing better debugging and more control over the scale of reliability of the product/program.

Salman Mehmood avatar Salman Mehmood avatar

Hello! I am Salman Bin Mehmood(Baum), a software developer and I help organizations, address complex problems. My expertise lies within back-end, data science and machine learning. I am a lifelong learner, currently working on metaverse, and enrolled in a course building an AI application with python. I love solving problems and developing bug-free software for people. I write content related to python and hot Technologies.

LinkedIn

Related Article - Python Exception

  • Open File Exception Handling in Python
  • Rethrow Exception in Python
  • Raise Exception in Python
  • Handle NameError Exception in Python
  • Python except Exception as e
  • Related Article - Python Unit Test

  • Python Unittest Setup
  • Python Mock Class Attribute
  • Python Unittest vs Pytest
  • Parameterized Unit Testing in Python
  • Python Unittest Discovery