How to Mock Class Attribute in Python
- Reason to Mock a Class Attribute
- Possible Solutions to Mock a Class Attribute
- Python Mock Class Constructor
This article’s primary aim is to demonstrate how to manipulate a class attribute using the python unit-testing module unittest
for testing and debugging purposes.
Reason to Mock a Class Attribute
Testing developed code for bugs, errors, and corner cases is one of the most important aspects when developing an application, primarily when the application is intended for multiple users.
Using the built-in Python module unittest
, we can carry out test cases to test our code’s integrity. One of the most common elements requiring rigorous testing is class attributes.
The class attribute can handle random inputs to prevent unexpected behaviour.
Consider the following code:
class Calculate:
value = 22 # Needs to be tested for different data types
def Process(self): # An example method
pass
Consider a class named Calculate
, which contains an attribute called value
and a method named Process
. A dictionary is stored inside the value
, which is later processed based on requirement and data type.
To ensure that the attribute can store almost any type of dictionary and is processed without errors, one must test the attribute to ensure that the implementation is error-free and does not need revisions.
Possible Solutions to Mock a Class Attribute
We can mock a class attribute in two ways; using PropertyMock
and without using PropertyMock
. Let’s learn each of them below using example code.
Use PropertyMock
to Mock a Class Attribute
To mock an attribute, we can use PropertyMock
, mainly intended to be used as a mock for a property or a descriptor for a class.
It is worth noting that PropertyMock
provides __get__
and __set__
methods to alter the return value of the property once it is fetched.
Consider the following code:
import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, PropertyMock
class Calculate:
value = 22 # Needs to be tested for different data types
def Process(self): # An example method
pass
class Test(unittest.TestCase):
@patch.object(Calculate, "value", new_callable=PropertyMock)
def test_method(self, mocked_attrib):
mocked_attrib.return_value = 1
# Will pass without any complaints
self.assertEqual(Calculate().value, 1)
print("First Assertion Passed!")
self.assertEqual(Calculate().value, 22) # Will throw an assertion
print("Second Assertion Passed!")
if __name__ == "__main__":
Test().test_method()
Output:
First Assertion Passed!
Traceback (most recent call last):
File "d:\Python Articles\a.py", line 24, 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 20, in test_method
self.assertEqual(Calculate().value, 22) # Will throw an assertion
File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
assertion_func(first, second, msg=msg)
File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
raise self.failureException(msg)
AssertionError: 1 != 22
In the solution, a new method, test_method
, is created to modify the value of Calculate.value
. It is vital to note that a function is decorated with a patch.object
.
The patch
decorator in the module helps patch modules and class-level attributes. To make what to patch a bit more specific, we use patch.object
instead of patch
to patch the method directly.
In the decorator, first, the class name Calculate
is passed, indicating that the object to be patched is a part of Calculate
with the name of the attribute value
being passed.
The last parameter is a PropertyMock
object, where we overwrite the value
attribute by passing a different number.
The general flow of the program is as follows:
test_method
is called.value
of classCalculate
is patched, with anew_callable
assigned, an instance ofPropertyMock
.Calculate.value
is overwritten with1
usingreturn_value
property.- The first assertion is checked, where
Calculate.value
must be equal to1
. - The second assertion is checked, where
Calculate.value
must be equal to22
.
Mock a Class Attribute Without Using PropertyMock
We can also resolve it without using PropertyMock
. Only slight modification to the above example is required.
Consider the following code:
import unittest
from unittest.mock import patch
class Calculate:
value = 22 # Needs to be tested for different data types
def Process(self): # An example method
pass
class Test(unittest.TestCase):
@patch.object(Calculate, "value", 1)
def test_method(self):
# Will pass without any complaints
self.assertEqual(Calculate().value, 1)
print("First Assertion Passed!")
# Will throw an assertion because "Calculate.value" is now 1
self.assertEqual(Calculate().value, 22)
print("Second Assertion Passed!")
if __name__ == "__main__":
Test().test_method()
Output:
First Assertion Passed!
Traceback (most recent call last):
File "d:\Python Articles\a.py", line 23, 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 19, in test_method
self.assertEqual(Calculate().value, 22) # Will throw an assertion because "Calculate.value" is now 1
File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
assertion_func(first, second, msg=msg)
File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
raise self.failureException(msg)
AssertionError: 1 != 22
Instead of passing an instance of PropertyMock
to new_callable
, we can directly give the value with which we wish to be stored into Calculate.value
.
Python Mock Class Constructor
Ensure that all initialized variables work as intended and do not exhibit unintended behaviour. It is also necessary to test constructors with varied inputs to reduce any corner cases.
Consider the following code:
class Calculate:
num = 0
dic = {}
def __init__(self, number, dictt):
self.num = number
self.dic = dictt
def Process(self): # An example method
pass
Let’s say we want to test the class Calculate
’s constructor. To ensure that the attributes work as intended, we must patch
the constructor and pass it with varied inputs to root out any possible errors.
How can we do that? See the following solution.
Use the patch.object
Decorator to Patch the Constructor
We can use the patch.object
decorator to patch the constructor.
import unittest
from unittest.mock import patch
from unittest.mock import MagicMock, PropertyMock
class Calculate:
num = 0
dic = {}
def __init__(self):
self.num = 1
self.dic = {"11", 2}
def Process(self): # An example method
pass
class Test(unittest.TestCase):
@patch.object(Calculate, "__new__")
def test_method(self, mocked_calc):
mocked_instance = MagicMock()
mocked_instance.num = 10
mocked_instance.dic = {"22": 3}
mocked_calc.return_value = mocked_instance
self.assertEqual(Calculate().num, 10)
self.assertEqual(Calculate().dic, {"22": 3})
print("First set of Asserts done!")
self.assertEqual(Calculate().num, 1)
self.assertEqual(Calculate().dic, {"11", 2})
print("Second set of Asserts done!")
if __name__ == "__main__":
Test().test_method()
Output:
The first set of Asserts is done!
Traceback (most recent call last):
File "d:\Python Articles\a.py", line 37, 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 32, in test_method
self.assertEqual(Calculate().num, 1)
File "C:\Program Files\Python310\lib\unittest\case.py", line 845, in assertEqual
assertion_func(first, second, msg=msg)
File "C:\Program Files\Python310\lib\unittest\case.py", line 838, in _baseAssertEqual
raise self.failureException(msg)
AssertionError: 10 != 1
It is worth noting that instead of patching __init__
, we have patched __new__
.
It is because the instance of a class is created when __new__
is executed, whereas in __init__
, only the variables are initialized. So, since we need to create a new mocked instance, why do we patch __new__
instead of __init__
?
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