Mocking in Python Using Unittest.mock

MockingUnittestFeatureImage

Mocking in Python is referred to the process of replacing original objects in a Python code with substitute objects, which can be discarded later on. Mocking is usually used in Unit Tests and Testing in Python. It saves the Test Developer’s time and energy and can replicate different libraries in Python and their methods. The Mock() object of unittest.mock class is highly used in Mocking objects in Python code. The developer can control the Mock object according to test requirements and set specifications, so as to warn the code developer of bugs and errors in the code.

The unittest.mock class also provides other objects such as MagicMock() and patch() as well which are used to create a different set of Unit Tests. The MagicMock() object provides stricter tests than the Mock() object. The patch() object is used as either a decorator or a context manager and is used in creating Unit Tests for objects available in a certain Scope.

Also read: Python unittest Module

The Mock() Object

The Mock() object available in Python unittest.mock module is a dynamic object and can mimic any library provided to it as an input. The object can also be declared as an individual object, without referencing any other library. It has a simple declaration as shown below:

mock=Mock( spec=None, side_effect=None, return_value= DEFAULT, wraps=None, name=None, spec_set=None, unsafe=False, **kwargs)

The arguments passed to the Mock() constructor above are the Default arguments and many of them can be modified by the user. Here is an explanation for all the arguments:

spec: A list of strings or available objects which acts as a specification for the mock object to access. Sends an AttributeError if non-existing objects are called from the specification

spec_set: It is a stricter variant of spec. The specifications once defined in spec_set cannot be later on modified.

return_value: It defines the output of certain methods present in a class. The developer can predefine the output required from the method to check the method’s testing and running.

unsafe: Added newly to the Mock() object, it returns an AttributeError whenever the mock object tries to access any attribute whose name starts with assert, assret, assrt, aseert or asert. Keeping it to True allows access to these attributes

side_effect: It is a function or an Exception that needs to be called whenever a Mock Object is called. It is useful for dynamically changing return Values.

wraps: It is used for items the Mock object wraps. The default is set to None.

name: It names the Mock Object. It is useful in debugging and the name will be propagated onto child mocks.

Mock() Object as a Library Substitute

The Mock() object can substitute a library and mock its methods and attributes and produce output accordingly. The benefit of using Mock over here is, the developer can predefine a dynamic output required for the successful execution of the Test.

The following code demonstrates the same:

from datetime import datetime
from unittest.mock import Mock
saturday=datetime(year=2023,month=2,day=4)
thursday=datetime(year=2023,month=2,day=2)
datetime=Mock()
def is_weekend():
    today=datetime.today()
    if today.weekday()==5 or today.weekday()==6:
        return True
    else:
        return False
datetime.today.return_value=saturday
assert is_weekend() 

In the above code, The Mock() object assumes the datetime Library on Line 5. In the rest of the code, datetime refers to the Mock() object created. The code checks the day which is today and returns whether it’s a weekend or not. A Unit Test to check the same would require the Test Developer to wait till Saturday arrives, to check whether the code perfectly works or not. But Mock() object saves the waiting time by assuming the datetime library and instructing the today() method of datetime to produce the output as Saturday as seen in Line 12.

Do notice an important thing, that assert keyword is used here to check the user-defined function is_weekend(). The assert keyword produces AssertionError, if the function is returning False as the output. It produces no Output if the function returns True as the output. It can be seen in the Code execution below, that the code produces no Output when Mock() object instructs the today() method to return output as Saturday.

AssertReturnsNoOutput
Assert Returns No Output when today() Returns Saturday
AssertReturnsAssertionError
Assert Returns AssertionError when today() Returns Thursday

This shows that Mock() object uses its return_value attribute to modify the output as per developer’s requirement. A developer can debug the code if an AssertionError is encountered.

Also Read: Assertions in Python

Side_effect in Mock()

A side effect in Mock() object is always executed whenever a Mock() object is called. It can be of various types, such as executing a function, calling an Exception, referencing an iterable, etc. The following code demonstrates three different types of Side Effects.

from unittest.mock import Mock
mock1=Mock(side_effect=TypeError('something'))
def plusone(x):
    return x+1
kv={'a':'alphabet','n':'number','s':'special character','c':'capital letters'}
def keypairs(arg):
    return kv[arg]
mock=Mock()
mock2=Mock()
mock.side_effect=plusone
mock2.side_effect=keypairs
print(mock(4))
print(mock2('a'))
print(mock1()) 

Here three different kinds of Side Effects have been demonstrated in one code. The Side Effects are:

  • Exception as a Side Effect
  • Iterable as a Side Effect
  • Function as a Side Effect

There are three different instances of Mock() object in the code, namely mock, mock1, and mock2. mock1 sets its Side Effect as a TypeError with the Error Message displayed as ‘something’. Exception-based Side Effects are used whenever the developer wants to stop the code execution after a Mock object has done its work. It usually saves time in debugging smaller parts of code, while ignoring more time-consuming parts of code to be debugged later on. In mock1, the Side Effect has been initialized in the declaration itself.

A user-defined function plusone() returns an integer value which is the given argument incremented by one. mock is another instance of Mock() object which uses plusone() as its Side Effect. Hence executing mock() with an integer argument shall always return the integer value incremented by one. It shall be noted, that the Side Effect has been set after the declaration of the Mock() object. Hence, a Side Effect for a Mock() object can be set anywhere in the code.

In the above code, a kv variable is an iterable dictionary. mock2 uses the iterable as its Side Effect. Even though mock2 uses a function to call the iterables, it can also be done without a function as well by using the iterable in the declaration itself. mock2 when run with an argument from the iterable produces the output from dictionary corresponding to the argument or returns an AttributeError if a non-existing Argument is passed.

The code after execution runs all three mock instances in order as mentioned in the code. It shows the following output.

SideEffectsOutput 1
Side Effects Output

Specifications in Mock()

The spec attribute in Mock() object is used to define specifications in Mock(). Specifications refer to classes or list of strings available to the Mock() object which it can access and work upon. It raises an AttributeError if a non-existing attribute is called by the Mock() object. Specifications are demonstrated in the following code.

from unittest.mock import Mock
class Student:
    def __init__(self,Name,rollno,Class,section,aggmarks):
        self.rollno=rollno
        self.Class=Class
        self.section=section
        self.aggmarks=aggmarks
        self.Name=Name
Student1=Student('Paan Singh Tomar',72,'CompSci','A',9.67)
mock=Mock(spec=Student1)
print(mock.Name,mock.rollno)
print(mock.age) 

In the above-given code, Student is a class that is specified as a Specification to the mock object, as depicted in Line 10. The code will first print the Mock Objects of Name and rollno attributes of the Student class as mentioned in the constructor in Line 9. But the output would be in form of Mock object. The code will also produce an AttributeError as the class Student doesn’t have the attribute age.

AttributeErrorasMockDoesntHaveAge 2
AttributeError as Mock Doesnt Have Age Attribute

The error can be removed by declaring an age attribute to the Mock object. It can be done by adding just one line of code in the above code between Lines 11 and 12.

mock.age=19

The now formatted code will produce the following output.

OutputAfterSettingAgeas19
Output After Setting Age as 19

Conclusion

After looking at the working and examples of Mock() object, it can be concluded that Mock object of unittest.mock library is instrumental in debugging a code. It assumes a substitute object in place of real-time objects and executes the code as per required by the developer. It is dynamic in nature and can run different instances of the Mock() object with variations at the same time.

Attributes such as side_effect, spec, and return_value play an important role in the Mock() object being dynamic. Hence it shall be preferred as the primary debugging tool by python developers.

References

https://docs.python.org/3/library/unittest.mock.html