Python unittest. Заменить 1 объект другим на время тестов
Как правильно заменить на время тестов объект my_bot (экземпляр класса Bot) на объект mockbot (экземпляр класса Mockbot) ?
Пояснение что-бы избежать XY вопроса:
Во время теста у меня есть объект mockbot, который при входящих запросах передается в обработчики.
Внутри обаботчиков создаются экземпляры разных классов (например "reg_user"), которые выполняют всю логику. При исполнении логики классы импортируют и используют настоящего бота, от чего тесты падают.
Конечно можно передавать параметр mockbot при создании экземпляра, но это похоже на кастыль, т.к. это всегда 1 и тот-же бот.
Код:
import unittest
from unittest.mock import patch
from postconfig import bot as my_bot
from ptbtest import Mockbot
@patch(my_bot, mockbot) # mockbot not exists here yet
class TestConversation(unittest.TestCase):
def setUp(self):
self.mockbot = Mockbot()
Дополнительный нюанс в том, что mockbot определяется только в функции setUp.
Я пробовал поиграться с аргументами (попеременно делая их строками, а не объектами, но всегда выдаются разные ошибки в связи с неправильным типом.
Складывается ощущение, что я неправильно использую patch и он не подменяет 2 объекта, а заменяет на дефолтную пустышку (mock).
P.S. я гуглил и читал доки, но все равно не понятно. https://pastebin.com/22Zwv239
Ответы (2 шт):
Попробуй навесить patch на конкретный тесткейс или запатчить в setUp и надо указывать полный путь до my_bot
import unittest
from unittest.mock import patch
from postconfig import bot as my_bot
from ptbtest import Mockbot
class TestConversation(unittest.TestCase):
def setUp(self):
self.mockbot = @patch("postconfig.my_bot") # здесь надо указать полный путь
self.mockbot.return_value = MockBot()
Вы совсем неправильно используете patch. Вот рабочий пример. Тут три файла в одном каталоге:
- bot.py - собственно модуль с определением бота
- bot_client.py - модуль использующий бот
- test_bot_client.py - тесты модуля
bot_client
# bot.py
class Bot:
def send(self):
print('in bot')
return 'bot result'
bot = Bot()
# bot_client.py
from bot import bot
def use_bot():
return bot.send()
# test_bot_client.py
import unittest
from unittest.mock import patch
from bot_client import use_bot
class MockedBot:
def send(self):
print('in mocked bot')
return 'mocked bot result'
class BotClientTest(unittest.TestCase):
def setUp(self):
self.mocked_bot = MockedBot()
self.patcher = patch('bot_client.bot', self.mocked_bot)
self.patcher.start()
def tearDown(self):
self.patcher.stop()
def test_bot_client(self):
result = use_bot()
self.assertEqual(result, 'mocked bot result')
if __name__ == '__main__':
unittest.main()
Самое важное и неочевидное в использовании mock-ов в питоне, когда необходимо заменить зависимость, это место где нужно использовать patch. Его нужно использовать не в месте определения объекта, который мы хотим замокать, а в месте, в котором мы этот объект используем.
То есть в данном примере, мы пишем тест для клиента бота и хотим бот замокать. Для этого нам нужно мокать именно переменную в модуле клиента, т.е. bot из bot_client.py, тот который там появился в результате импорта.
Причина этого проста. Если мы будет пытаться замокать именно bot из bot.py, то у нас ничего не выйдет, так как в момент, когда мы это делаем (т.е. в setUp методе теста), импорт bot в bot_client.py уже произошел (это случилось, когда мы в тесте делали импорт bot_client.use_bot, а модуль bot_client в свою очередь импортировал bot.bot) и замена bot в модуле bot уже нечего не дает (в bot_client переменная bot содержит оригинальное значение и наша замена переменно в bot никак на это не влияет).