Mocking

The problem

from datetime import datetime


def is_weekday():
    today = datetime.today()
    return 0 <= today.weekday() < 5


assert is_weekday()

Monkey Patching

def is_weekday():
    today = datetime.today()
    return 0 <= today.weekday() < 5


datetime.today = lambda: datetime.datetime(year=2020, day=22, month=4)

assert is_weekday()

Monkey patching is the replacement of one object with another at runtime.

Mocks

import datetime
from unittest.mock import Mock

friday = datetime.datetime(year=2020, month=4, day=24)

datetime = Mock()

def is_weekday():
    today = datetime.datetime.today()
    return (0 <= today.weekday() < 5)

datetime.datetime.today.return_value = friday
assert is_weekday()

Recursion

A Mock must simulate any object that it replaces. To achieve such flexibility, it creates its attributes when you access them

json = Mock()                                                                                                                                                                          
# <Mock id='140452118049488'>

json.dumps()                                                                                                                                                                           
# <Mock name='mock.dumps()' id='140452118553488'>

json.dumps().get('student').id                                                                                                                                                         
# <Mock name='mock.dumps().get().id' id='140452116708560'>

Methods & Properties

  • my_mock.assert_called()
  • my_mock.assert_called_once()
  • my_mock.assert_called_with(*args, **kwargs)
  • my_mock.assert_called_once_with(*args, **kwargs)
  • my_mock.call_count
  • my_mock.call_args_list

Managing Return values

import datetime
from unittest.mock import Mock

friday = datetime.datetime(year=2020, month=4, day=24)
saturday = datetime.datetime(year=2020, month=4, day=25)

datetime = Mock()

def is_weekday():
    today = datetime.datetime.today()
    return (0 <= today.weekday() < 5)

datetime.datetime.today.return_value = friday
assert is_weekday()

datetime.datetime.today.return_value = saturday
assert not is_weekday()

Managing Side effects

from requests.exceptions import Timeout
from unittest.mock import Mock

requests = Mock()

def get_cat_facts():
    r = requests.get('https://cat-fact.herokuapp.com/facts')
    if r.status_code == 200:
        return r.json()

class TestGetCats(unittest.TestCase):
    def test_get_cats_timeout(self):
        requests.get.side_effect = Timeout

        with self.assertRaises(Timeout):
            get_cat_facts()

patch() 

as decorator

from unittest.mock import patch
from datetime import datetime

from calendar import is_weekday


class CalendarTests(unittest.TestCase):
    @patch('calendar.datetime.datetime')
    def test_sunday_is_not_weekday(self, datetime_mock):
        datetime_mock.today.return_value = datetime(year=2020, month=4, day=26)

        self.assertFalse(is_weekday())

patch() 

as context manager

from unittest.mock import patch
from datetime import datetime

from calendar import is_weekday


class CalendarTests(unittest.TestCase):
    def test_is_weekday(self):
        with patch('calendar.datetime.datetime') as datetime_mock:
            sunday = datetime(year=2020, month=4, day=26)
            datetime_mock.today.return_value = sunday
            self.assertFalse(is_weekday())

        with patch('calendar.datetime.datetime') as datetime_mock:
            wednesday = datetime(year=2020, month=4, day=22)
            datetime_mock.today.return_value = wednesday
            self.assertTrue(is_weekday())

Where to Patch?

Example

 
from datetime import datetime


my_datetime = datetime


def is_weekday():
    today = my_datetime.today()
    return (0 <= today.weekday() < 5)

Change the tests

 
import unittest
from unittest.mock import patch
from datetime import datetime

from calendar import is_weekday


class CalendarTests(unittest.TestCase):
    @patch('calendar.datetime')
    def test_sunday_is_not_weekday(self, datetime_mock):
        sunday = datetime(year=2020, month=4, day=26)
        datetime_mock.today.return_value = sunday

        self.assertFalse(is_weekday())


if __name__ == '__main__':
    unittest.main()
AssertionError: True is not false

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)

Correct the tests

 
import unittest
from unittest.mock import patch
from datetime import datetime

from calendar import is_weekday


class CalendarTests(unittest.TestCase):
    @patch('calendar.my_datetime')
    def test_sunday_is_not_weekday(self, datetime_mock):
        sunday = datetime(year=2020, month=4, day=26)
        datetime_mock.today.return_value = sunday

        self.assertFalse(is_weekday())


if __name__ == '__main__':
    unittest.main()
╰─Ѫ py mocks/test_calendar.py                                                                                                                                                              1 ↵
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK

Mock problems

 
import unittest
from unittest.mock import patch
from datetime import datetime

from calendar import is_weekday


class CalendarTests(unittest.TestCase):
    @patch('calendar.my_datetime')
    def test_sunday_is_not_weekday(self, datetime_mock):
        sunday = datetime(year=2020, month=4, day=26)
        datetime_mock.tdoay.return_value = sunday

        self.assertFalse(is_weekday())


if __name__ == '__main__':
    unittest.main()
TypeError: '<=' not supported between instances of 'int' and 'MagicMock'

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (errors=1)

Mock specifications

 
import unittest
from unittest.mock import patch
from datetime import datetime

from calendar import is_weekday


class CalendarTests(unittest.TestCase):
    @patch('calendar.my_datetime', spec=datetime)  # or autospec=True
    def test_sunday_is_not_weekday(self, datetime_mock):
        sunday = datetime(year=2020, month=4, day=26)
        datetime_mock.tdoay.return_value = sunday

        self.assertFalse(is_weekday())


if __name__ == '__main__':
    unittest.main()
AttributeError: Mock object has no attribute 'tdoay'

----------------------------------------------------------------------
Ran 1 test in 0.015s

FAILED (errors=1)

Python 101 9th Mocking

By Hack Bulgaria

Python 101 9th Mocking

  • 946