All Articles

pytest-mock sample들

required

pip install pytest pytest-mock

pytest의 플러그인인 pytest-mock을 통해 mocking을 해보도록 하겠습니다.

폴더구조와 구성은 다음 과 같습니다.

sample

# mocking_const.py
CONST = 10000
# mocking_export.py
class SomethingExport:
    val1 = 1000
    val2 = 1000
    val3 = 1000

    def val(self):
        return sum([self.val1, self.val2, self.val3])

def some_export_func():
    return {"message": "SUCCESS"}
# mocking_import.py
from example.mocking_const import CONST

class SomethingImport:
    val1 = 100
    val2 = 200
    val3 = 300

    def val(self):
        return sum([self.val1, self.val2, self.val3, CONST])

def some_import_func():
    return {"message": "SUCCESS"}
# mocking_import2.py
from mocking_export import SomethingExport, some_export_func

class SomethingImport2:
    val1 = 100
    val2 = 200
    val3 = 300

    def val(self):
        return sum([self.val1, self.val2, self.val3, SomethingExport().val()])

    def func(self):
        return some_export_func()
# mocking_tests.py
import pytest

from example.mocking_import import SomethingImport
from example.mocking_import2 import SomethingImport2

class Something:
    val1 = 100
    val2 = 200
    val3 = 300

    def val(self):
        return sum([self.val1, self.val2, self.val3])

def some_func():
    return {"message": "SUCCESS"}

class TestMockingClass:
    """pytest-mock 라이브러리 mocker fixture 예시입니다."""

    def test_patch_direct_func(self, mocker):
        """
        직접 함수 리턴 값을 조작할 경우에는 MagicMock 객체를 활용합니다.
        .return_value를 통해 ()으로 호출시 return할 값을 지정할 수 있습니다.
        """
        some_func = mocker.MagicMock()
        some_func.return_value = {"message": "patched"}
        assert some_func() == {"message": "patched"}

    def test_patch_direct_func2(self, mocker):
        """
        MagicMock 초기화시에 return_values를 kwargs로 입력해도 됩니다.
        """
        some_func = mocker.MagicMock(return_value={"message": "patched"})
        assert some_func() == {"message": "patched"}

    def test_patch_imported_func(self, mocker):
        """
        tests.py 모듈 밖에서의 동작에 관여하려면, mocker.patch()를 사용합니다.
        ()으로 호출시 return할 값을 지정할 수 있습니다
        patch는 해당 라인 동작시에 동작합니다.
        _tests.py 모듈에 먼저 import 되어 있을경우 patch 동작이 기대한대로 동작하지 않습니다.
        patch() 후 import 하여야 합니다.
        """
        mocker.patch("example.mocking_import.some_import_func", return_value={"message": "patched"})
        from example.mocking_import import some_import_func

        assert some_import_func() == {"message": "patched"}

    def test_patch_func_hook(self, mocker):
        """
        tests.py 모듈 밖에서의 동작을 mocking 하려면, mocker.patch()를 사용합니다.
        ()으로 호출시 return할 값을 지정할 수 있습니다 return_value=<value>
        B.py 모듈에서 A.py module의 func_a 함수를 가져다 사용할경우, 
        func_a 함수를 임포트한 B.py 모듈에서 patch 해야 합니다.
        단, 
        from A import func_a 가 아니라
        import A 
        A.func_a 
        로 사용한 경우에는, A를 patch 해야 합니다.
        """
        mocker.patch("example.mocking_import2.some_export_func", return_value={"message": "patched"})
        from example.mocking_import2 import SomethingImport2

        assert SomethingImport2().func() == {"message": "patched"}

    def test_patch_objcect_class_method_and_attr_direct_reference(self, mocker):
        """ _tests.py 에 정의된 Something.val patch.object를 이용해 mocking 합니다."""

        # patch가 아닌, patch.object 를 사용합니다.
        mocker.patch.object(Something, "val")

        # some 은 Something의 instance 입니다.
        some = Something()

        # some.val 은 Mock 객체입니다.
        # some.val.return_value 를 통해서 값을 지정할 수 있습니다.
        # mocker.patch.object(Something, "val", return_value=9999) 도 동일한 결과를 만듭니다.
        some.val.return_value = 9999

        assert some.val(1, 2, 3, 4, 5) == 9999
        assert isinstance(some, Something)
        # mock 객체가 호출되었는지 assert 합니다.
        some.val.assert_called()

        # mock 객체가 지정한 인자들과 호출되었는지 assert 합니다.
        some.val.assert_called_once_with(1, 2, 3, 4, 5)

    def test_patch_objcect_instance_attr_direct_reference(self, mocker):
        """ _tests.py 에 정의된 Something.val3 patch.object를 이용해 mocking 합니다."""

        # patch.object(class, attr_name, val) 을 쓸 경우, 
				# instance.attr_val == val 입니다.
        mocker.patch.object(Something, "val3", 100000)
        some = Something()
        assert some.val3 == 100000

    def test_patch_object_const_direct(self, mocker):
        """ 외부 모듈에 에 정의된 CONST 를 mocking 합니다."""

        #  SomthingImport가 사용하는 mocking_import의 CONST를 현재 모듈에서 patch.object() 로 20000 으로  patch 합니다.
        import example.mocking_import as mi

        mocker.patch.object(mi, "CONST", 20000)
        some = SomethingImport()

        assert some.val() == 20600

    def test_patch_const_hook(self, mocker):
        #  SomthingImport가 정의된 module에서 CONST를 patch() 로 20000 으로 patch 합니다.
        mocker.patch("example.mocking_import.CONST", 20000)
        some = SomethingImport()
        assert some.val() == 20600

    def test_patch_object_with_direct_import(self, mocker):
        """import 한 SomethingImport.val이 mock 객체를 반환하도록 patch 합니다. """
        mocker.patch.object(SomethingImport, "val", return_value=30600)
        some_mocked = SomethingImport()
        assert some_mocked.val() == 30600

    def test_patch_with_direct_import(self, mocker):
        """
        mocker.patch를 사용하여 import 한 SomethingImport patch
        """
        mocker.patch("example.mocking_import.SomethingImport")
        # patch를 걸고, SomthingImport를 import 해야 합니다.
        # 해당 모듈레벨에서 SomthingImport를 import 하고 있다면
        # 이름을 다르게 하지 않으면 모듈 레벨에서 Import 한 SomethingImport(Not Mock)를 호출한다.
        from example.mocking_import import SomethingImport as SI

        instance = SI.return_value
        instance.val.return_value = 30600
        some_mocked = SI()
        assert some_mocked.val() == 30600

    def test_patch_other_module_hook(self, mocker):
        "SomethingExport가 정의된 module이 아닌, SomethingExport를 사용하는 module에서 원하는 값이 나오도록 patch 한다. "
        
        # mock 객체가 리턴할 값을 mock 객체로 지정합니다.
        mocked_something_export = mocker.patch("example.mocking_import2.SomethingExport")

        # mock 객체가 리턴할 값을 지정합니다.
        instance = mocked_something_export.return_value
        # mock 객체의 val method가 리턴할 값을 지정합니다.
        instance.val.return_value = 6000

        # patch 한 SomethingExport를 사용하는 SomethingImport2를 초기화합니다.
        something_import2 = SomethingImport2()
        assert something_import2.val() == 6600

    def test_side_effect(self, mocker):
        """
        단순 값의 return이 아닌, 원하는 함수 동작을 지정할 수 도 있습니다.
        Mock(side_effect=<func>) 와 동일합니다.
        Exception('Boom!')
        """
        def _side_1():
            return "SIDE_WORKING"
        
        def _side_2():
            raise Exception("Error")	
        
        # Something의 instance.val 이 _side_1을 call 하도록 만든다
        mocker.patch.object(Something, "val", side_effect=_side_1)
        some = Something()
        assert some.val() == "SIDE_WORKING"

        # Something의 instance.val 이 _side_2(raise Exception)을 하도록 만든다
        mocker.patch.object(Something, "val", side_effect=_side_2)
        some2 = Something()
        # pytest raise 처리
        with pytest.raises(Exception):
            some2.val()