Pytest + mock: патч не работает без предложения with

Я тестирую сложную логику, которая требует объединения центральной таблицы фактов с 10-20 таблицами меньшего размера. Я хочу издеваться над этими 10-20 таблицами меньшего размера.

Как исправить методы, возвращающие значения в цикле for? См. код ниже.

tables.py:

class BaseClass(object):

  def load(path: str):
    ...

class SmallTable1(BaseClass):


  def load():
     super().load(self.path)

class SmallTable20(BaseClass):

  def load():
     super().load(self.path)

test_logic.py

# QUESTION - how to make it work
def test_not_work(datasets):
   for i in range(1, 21):
      table = 'SmallTable' + str(i)
      with mock.patch('some_package.tables.{}.load'.format(table)) as load_mock:
         load_mock.return_value = datasets[table]
      do_joins()  # here my mocks doesn't work


def test_works(datasets):
   with mock.patch('some_package.tables.SmallTable1.load'.format(i)) as load_mock_1:
       load_mock_1.return_value = datasets[SmallTable1]

       with mock.patch('some_package.tables.SmallTable2.load'.format(i)) as load_mock_2:
            load_mock_2.return_value = datasets[SmallTable2]

            .....  # repeat the same 8-18 times more

                                     do_joins()  # here my mocks do work, but with cumbersome code and huge right offset

P.S. в качестве альтернативы я могу попытаться издеваться над BaseClass.load, но тогда я не знаю, как вернуть другой набор данных для другой таблицы (класса).


person VB_    schedule 13.05.2020    source источник


Ответы (1)


Если предположить, что do_join будет вызываться вне цикла, со всеми исправлениями таблиц, вы можете написать фикстуру, которая использует contextlib.ExitStack для настройки всех моков:

from contextlib import ExitStack
from unittest import mock   
import pytest

from some_package import do_join


@pytest.fixture
def datasets():
    ...    

@pytest.fixture
def mock_tables(datasets):
    with ExitStack() as stack:
        for i in range(1, 21):
            table = 'SmallTable' + str(i)
            load_mock = stack.enter_context(
                mock.patch('some_package.tables.{}.load'.format(table)))
            load_mock.return_value = datasets[table]
        yield


def test_join(mock_tables):
    do_join()

Это означает, что все макеты все еще активны во время выхода и будут удалены только после завершения теста.

Если у вас установлен pytest-mock, вы можете использовать вместо него фикстуру mocker:

@pytest.fixture
def mock_tables(datasets, mocker):
    for i in range(1, 21):
        table = 'SmallTable' + str(i)
        load_mock = mocker.patch('some_package.tables.{}.load'.format(table))
        load_mock.return_value = datasets[table]
    yield
person MrBean Bremen    schedule 14.05.2020