Python unit testing with Mock

Unit testing is used to check that a certain unit of code behaves as expected. This unit should have a narrow, well-defined scope and it is important that the units are tested in isolation, such as by stubbing or mocking interactions to the outside world. By testing individual units in isolation from external code they depend upon, failures in the code base can be more easily identified. To avoid individual tests breaking unnecessarily, this concept of keeping unit tests decoupled extends to e.g. assigning expected return values to the values of object attributes instead of ‘hard-coded’ values.

In the following example, the unittest.mock library allows a function to be tested in isolation from a helper function which appends a string with the date in a custom format. Here, the call to the helper function is mocked using the convenient patch decorator:

# views.login_view.py
from datetime import datetime

def suffix(day):
    if day in ['11', '12', '13']:
        return 'th'
    return {1: 'st', 2: 'nd', 3: 'rd'}.get(day, 'th')

def welcome_msg(greet):
    dt = datetime.now()
    day = str(dt.day) + suffix(dt.day)
    today = dt.strftime('{d} %B %Y').replace('{d}', day)
    return '{}. Today is {}'.format(greet, today)

# tests.py
from datetime import datetime
import unittest
from unittest.mock import patch
from views.login_view import welcome_msg

class LoginViewTestCase(unittest.TestCase):

    @patch('views.login_view.suffix')
    def test_greeting(self, suffix_patch):
        suffix_patch.return_value='[a-z][a-z]'
        expected = '{}. Today is {}{} {}'.format(
            'Hello', datetime.now().day, 'th', 
            datetime.now().strftime('%B %Y'))
        self.assertRegex(expected, welcome_msg('Hello'))

In doing so, the mocked function has been replaced with a Mock object which was created by applying the decorator. When it is called, a Mock object will return its return_value attribute, which can easily be set but by default is a new Mock object.

It is desirable in many cases to test if and how many times a mocked callable is called. The boolean and integers values provided by call and call_count are useful for this:

mock = Mock(return_value=None)
a - mock.called
mock()
mock()
b = mock.called
c = mock.call_count

>>> print(a, b, c)
False True 2

side_effect can be set and this is useful for raising exceptions in order to test error handling:

from django.http import Http404

@patch('views.login_view.requests.get', side_effect=Http404)
def test_my_func_raises_http_exception(self, my_patch):
    with self.assertRaises(Http404):
        self.my_func(url)

It is also useful where your mock is going to be called several times, and you want each call to return a different value:

def adder(val):
    return val + 5

def adder_squared():
    return adder(a) ** 2

@patch('adder', side_effect=[1, 2])
def test_repeat_caller(self, test_patch):
    resp = adder_squared()
    self.assertEqual(resp, 1)
    resp2 = adder_squared()
    self.assertEqual(resp2, 4)
Advertisements

Leave a Reply