Как настроить мок-объекты с использованием контекстных менеджеров в `unittest.mock`?

Для настройки мок-объектов с использованием контекстных менеджеров в unittest.mock используйте patch, patch.object, patch.multiple и подобные функции в блоке with. Контекстный менеджер автоматически выполняет start() при входе в блок with и stop() при выходе, гарантируя, что моки будут активны только в пределах этого блока и восстановлены после его завершения.

Контекстные менеджеры в `unittest.mock` предоставляют удобный способ автоматической настройки и восстановления мок-объектов в пределах определенного блока кода. Это особенно полезно для написания более чистого и читаемого кода, а также для предотвращения ошибок, связанных с забытым восстановлением исходных объектов.

Основные контекстные менеджеры, предоставляемые `unittest.mock`, это `patch`, `patch.object`, `patch.multiple`, и `patch.dict`. Каждый из них позволяет временно заменить объект, атрибут объекта, несколько атрибутов объекта или словарь соответственно, мок-объектом на время выполнения блока `with`. После выхода из блока `with`, оригинальное состояние восстанавливается автоматически.

Примеры:


 import unittest
 from unittest.mock import patch, Mock
 import os

 class MyClass:
  def my_method(self):
   return os.path.exists('/tmp/my_file')

 class TestMyClass(unittest.TestCase):
  def test_my_method_with_patch(self):
   my_object = MyClass()

   # Используем patch как контекстный менеджер для замены os.path.exists
   with patch('os.path.exists', return_value=True) as mock_exists:
    result = my_object.my_method()
    self.assertTrue(result)
    mock_exists.assert_called_once_with('/tmp/my_file')

   # После выхода из блока with, os.path.exists восстановлен в исходное состояние

  def test_my_method_with_patch_object(self):
   my_object = MyClass()

   # Используем patch.object как контекстный менеджер для замены метода my_method
   with patch.object(my_object, 'my_method', return_value=True) as mock_method:
    result = my_object.my_method()
    self.assertTrue(result)
    mock_method.assert_called_once()

   # После выхода из блока with, my_object.my_method восстановлен

  def test_patch_multiple(self):
        with patch.multiple(os.path, exists=Mock(return_value=True), isdir=Mock(return_value=False)) as mocks:
            exists_mock = mocks['exists']
            isdir_mock = mocks['isdir']

            self.assertTrue(os.path.exists('/tmp/something'))
            self.assertFalse(os.path.isdir('/tmp/something'))

            exists_mock.assert_called_once()
            isdir_mock.assert_called_once()

  def test_patch_dict(self):
      my_dict = {'a': 1, 'b': 2}

      with patch.dict(my_dict, {'a': 3, 'c': 4}):
          self.assertEqual(my_dict['a'], 3)
          self.assertEqual(my_dict['b'], 2)
          self.assertEqual(my_dict['c'], 4)

      self.assertEqual(my_dict['a'], 1)
      self.assertEqual(my_dict['b'], 2)
      self.assertNotIn('c', my_dict)


 if __name__ == '__main__':
  unittest.main()
  

Объяснение:

  • `with patch('os.path.exists', return_value=True) as mock_exists:` Здесь мы используем `patch` для замены функции `os.path.exists`. `'os.path.exists'` указывает путь к объекту, который нужно заменить. `return_value=True` указывает, что мок-объект должен возвращать `True` при каждом вызове. `as mock_exists` присваивает мок-объект переменной `mock_exists`, которую можно использовать для проверки вызовов.
  • `with patch.object(my_object, 'my_method', return_value=True) as mock_method:` Похожий пример, но теперь мы заменяем метод `my_method` объекта `my_object`.
  • Контекстный менеджер гарантирует, что после завершения блока `with`, функция `os.path.exists` или метод `my_method` будет восстановлен в исходное состояние, независимо от того, произошла ли ошибка внутри блока.
  • `patch.multiple` позволяет заменить несколько объектов/аттрибутов одновременно.
  • `patch.dict` позволяет заменить содержимое словаря.

Использование контекстных менеджеров делает тесты более чистыми, лаконичными и менее подверженными ошибкам при настройке и очистке мок-объектов.

0