Python Mock Class Attribute

Python Mock Class Attribute

  1. Reason to Mock a Class Attribute
  2. Possible Solutions to Mock a Class Attribute
  3. 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
        self.assertEqual(Calculate().value, 1) # Will pass without any complaints
        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:

  1. test_method is called.
  2. value of class Calculate is patched, with a new_callable assigned, an instance of PropertyMock.
  3. Calculate.value is overwritten with 1 using return_value property.
  4. The first assertion is checked, where Calculate.value must be equal to 1.
  5. The second assertion is checked, where Calculate.value must be equal to 22.

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__?

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 Unit Test

  • Python Unittest Setup
  • Python Unittest vs Pytest
  • Python Mock Raise Exception
  • Parameterized Unit Testing in Python
  • Python Unittest Discovery