# Karl O. Pinc <kop@karlpinc.com>
-from pytest import fixture
from unittest import mock
+import functools
+import sys
+import warnings
+import pytest
+
+
+# Utility decorator
+
+# Pyramid 2.0 added deprecation warnings to pyramid.request.Request.
+# These express when unittest.mock accesses the class' attributes
+# to construct a mock, when the mock_request_blank fixture is used
+# and a mock made. Ignore all warnings triggered by unittest.mock.
+def ignore_deprecation_warnings(ignore):
+ def identity(func):
+ return func
+
+ def wrapper(func):
+ @functools.wraps(func)
+ def wrapped(*args, **kwargs):
+ with warnings.catch_warnings():
+ warnings.filterwarnings(
+ "ignore", category=DeprecationWarning,
+ module='unittest.mock')
+ return func(*args, **kwargs)
+ return wrapped
+
+ if ignore:
+ return wrapper
+ return identity
# Mock support
-def make_mock_fixture(module, method, autouse=False, wraps=None):
+def make_mock_fixture(module, method,
+ autouse=False, wraps=None, ignore_deprecation=True):
'''Returns a pytest fixture that mocks a module's method or a class's
class method. "module" is a module or a class, but method is a string.
'''
- @fixture(autouse=autouse)
- def fix(monkeypatch):
- mocked = mock.Mock(
+ @ignore_deprecation_warnings(ignore_deprecation)
+ def make_mock():
+ return mock.Mock(
spec=getattr(module, method), name=method, wraps=wraps)
+
+ @pytest.fixture(autouse=autouse)
+ def fix(monkeypatch):
+ mocked = make_mock()
monkeypatch.setattr(module, method, mocked)
return mocked
return fix
-def make_magicmock_fixture(module, method, autouse=False, autospec=False):
+def make_magicmock_fixture(
+ module, method,
+ autouse=False, autospec=False, ignore_deprecation=True):
'''Returns a pytest fixture that magic mocks a module's method or a
class's class method. "module" is a module or a class, but method
is a string.
'''
- @fixture(autouse=autouse)
- def fix(monkeypatch):
+ @ignore_deprecation_warnings(ignore_deprecation)
+ def make_mocked():
if autospec:
- mocked = mock.create_autospec(
+ return mock.create_autospec(
getattr(module, method), spec_set=True)
- else:
- mocked = mock.MagicMock(
- spec=getattr(module, method), name=method)
+ return mock.MagicMock(
+ spec=getattr(module, method), name=method)
+
+ @pytest.fixture(autouse=autouse)
+ def fix(monkeypatch):
+ mocked = make_mocked()
monkeypatch.setattr(module, method, mocked)
return mocked
return fix
+def function_mock_fixture(method, ignore_deprecation=True):
+ '''Returns a pytest fixture that mocks a function.
+ "method" is the actual function.
+ The primary purpose is to monkeypatch modules
+ that are needed by python's mechanics.
+ '''
+ name = method.__name__
+ module = sys.modules[method.__module__]
+
+ @ignore_deprecation_warnings(ignore_deprecation)
+ def make_mocked():
+ return mock.Mock(spec=module, name=name)
+
+ @pytest.fixture
+ def fix(monkeypatch):
+ def run():
+ mocked = make_mocked()
+ func = getattr(mocked, name)
+ monkeypatch.setattr(module, name, func)
+ return func
+ return run
+ return fix
+
+
+def instance_mock_fixture(method, ignore_deprecation=True):
+ '''Returns a pytest fixture that mocks a instance method.
+ "method" is the actual instance or class method.
+ The primary purpose is to monkeypatch classes
+ that are needed by python's mechanics.
+ '''
+ name = method.__name__
+ cls = method.__self__
+
+ @ignore_deprecation_warnings(ignore_deprecation)
+ def make_mocked():
+ return mock.Mock(spec=cls, name=name)
+
+ @pytest.fixture
+ def fix(monkeypatch):
+ def run():
+ mocked = make_mocked()
+ func = getattr(mocked, name)
+ monkeypatch.setattr(cls, name, func)
+ return func
+ return run
+ return fix
+
+
def late_instance_mock_fixture(method, ignore_deprecation=True):
'''Returns a pytest fixture that mocks a instance method.
"method" is the name of the instance or class method.
The function returned by the fixture takes the class instance to
be monkeypatched.
- The fixture is called by the test function with the class instance
- that's to be monkeypatched and the mock is returned for the
- test function to configure/etc.
+ Useful to monkeypatch classes produced by fixtures.
'''
- @fixture
+ @ignore_deprecation_warnings(ignore_deprecation)
+ def make_mocked(cls):
+ return mock.Mock(spec=cls, name=method)
+
+ @pytest.fixture
def fix(monkeypatch):
def run(cls):
- mocked = mock.Mock(spec=getattr(cls, method), name=method)
- monkeypatch.setattr(cls, method, mocked)
- return mocked
+ mocked = make_mocked(cls)
+ func = getattr(mocked, method)
+ monkeypatch.setattr(cls, method, func)
+ return func
return run
return fix
-# Copyright (C) 2020, 2021 The Meme Factory, Inc. http://www.karlpinc.com/
+# Copyright (C) 2020, 2021, 2024 The Meme Factory, Inc.
+# http://www.karlpinc.com/
# This file is part of PGWUI_Develop.
#
pgwui.mako.exceptions, 'text_error_template')
MockTemporaryDirectory = testing.make_magicmock_fixture(
pgwui.tempfile, 'TemporaryDirectory')
-mock_set_extraction_path = testing.instance_method_mock_fixture(
- 'set_extraction_path')
-mock_resource_filename = testing.instance_method_mock_fixture(
- 'resource_filename')
-mock_cleanup_resources = testing.instance_method_mock_fixture(
- 'cleanup_resources')
+mock_set_extraction_path = testing.function_mock_fixture(
+ pgwui.pkg_resources.set_extraction_path)
+mock_resource_filename = testing.function_mock_fixture(
+ pgwui.pkg_resources.resource_filename)
+mock_cleanup_resources = testing.function_mock_fixture(
+ pgwui.pkg_resources.cleanup_resources)
mock_scandir = testing.make_magicmock_fixture(
pgwui.os, 'scandir')
mock_cleanup_resources):
'''All the mocks are called
'''
- mocked_set_extraction_path = mock_set_extraction_path(pgwui.pkg_resources)
- mocked_resource_filename = mock_resource_filename(pgwui.pkg_resources)
- mocked_cleanup_resources = mock_cleanup_resources(pgwui.pkg_resources)
+ mocked_set_extraction_path = mock_set_extraction_path()
+ mocked_resource_filename = mock_resource_filename()
+ mocked_cleanup_resources = mock_cleanup_resources()
pgwui.deliver_target(None, None)
import pytest
-
import sys
+import tested_module
from pgwui_develop import testing
# Test functions
+#
+# ignore_depreciation_warnings()
+#
+
+
+@pytest.mark.parametrize(
+ ('ignore', 'warn_cnt'),
+ [pytest.param(
+ 'True', 0,
+ marks=pytest.mark.xfail(
+ reason="For reasons unknown, pytester reports 1 warnings")),
+ ('False', 1)])
+@pytest.mark.integrationtest
+@pytest.mark.unittest
+def test_ignore_deprecation_warnings_ignore(pytester, ignore, warn_cnt):
+ '''The expected number of deprecation warnings are raised when warnings
+ are ignored, or not
+ '''
+
+ pytester.makepyfile(
+ f"""
+ import pytest
+ import warnings
+ from unittest import mock
+ from pgwui_develop import testing
+
+ # Class which raises a deprecation warning
+ class WithDeprecation():
+ '''Test class that raises a DeprecationWarning when mocked
+ '''
+ def __getattribute__(self, name):
+ if name == 'oldmethod':
+ warnings.warn('oldmethod is deprecated',
+ category=DeprecationWarning)
+ return super().__getattribute__(name)
+
+ def oldmethod(self):
+ return "I am old, so old."
+
+ mocked_oldmethod = testing.make_mock_fixture(
+ WithDeprecation, 'oldmethod',
+ ignore_deprecation={ignore})
+
+ def test_ignore_deprecation_warnings_ignore(mocked_oldmethod):
+ expected = 'something'
+ mocked_oldmethod.return_value = expected
+ assert WithDeprecation().oldmethod() == expected
+ """)
+
+ result = pytester.runpytest()
+
+ result.assert_outcomes(passed=1, warnings=warn_cnt)
+
+
#
# make_mock_fixture()
#
#
-# instance_method_mock_fixture()
+# function_mock_fixture()
#
+f_mocked_method = testing.function_mock_fixture(tested_module.method_to_mock)
+
+
+@pytest.mark.unittest
+@pytest.mark.integrationtest
+def test_function_mock_fixture(f_mocked_method):
+ # The mock of the instance method works
+
+ test_value = 'mocked value'
+ f_mocked_method().return_value = test_value
+
+ result = tested_module.method_to_mock()
+
+ assert result == test_value
+
+
+@pytest.mark.unittest
+@pytest.mark.integrationtest
+def test_function_mock_fixture_unmocked():
+ # The test function works after the mocking
+
+ result = tested_module.method_to_mock()
+
+ assert result == tested_module.normal_return_value
+
+
+#
+# Setup for mocking instance methods
+#
normal_return_value = 'not mocked'
return normal_return_value
-mocked_method = testing.instance_method_mock_fixture('method_to_mock')
+test_instance = TestClass()
+
+
+#
+# instance_mock_fixture()
+#
+i_mocked_method = testing.instance_mock_fixture(test_instance.method_to_mock)
+
+
+@pytest.mark.unittest
+@pytest.mark.integrationtest
+def test_instance_mock_fixture(i_mocked_method):
+ # The mock of the instance method works
+
+ test_value = 'mocked value'
+ i_mocked_method().return_value = test_value
+
+ result = test_instance.method_to_mock()
+
+ assert result == test_value
+
+
+@pytest.mark.unittest
+@pytest.mark.integrationtest
+def test_instance_mock_fixture_unmocked():
+ # The test function works after the mocking
+
+ result = test_instance.method_to_mock()
+
+ assert result == normal_return_value
#
--- /dev/null
+# Copyright (C) 2024 The Meme Factory, Inc.
+# http://www.karlpinc.com/
+
+# This file is part of PGWUI_Develop.
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public
+# License along with this program. If not, see
+# <http://www.gnu.org/licenses/>.
+#
+
+# Karl O. Pinc <kop@karlpinc.com>
+
+normal_return_value = 'not mocked'
+
+
+def method_to_mock():
+ return normal_return_value
markers =
unittest
integrationtest
+pytest_plugins =
+ pytester