diff --git a/Lib/test/test_unittest/testmock/testpatch.py b/Lib/test/test_unittest/testmock/testpatch.py index bd85fdcfc472a61..bd2a7fb6d6731d7 100644 --- a/Lib/test/test_unittest/testmock/testpatch.py +++ b/Lib/test/test_unittest/testmock/testpatch.py @@ -5,6 +5,7 @@ import os import sys from collections import OrderedDict +from types import LazyImportType, ModuleType import unittest import test @@ -2101,5 +2102,39 @@ def test(_): test() +class PatchLazyImportTest(unittest.TestCase): + + def lazy_module(self): + # `lazy from` binds eagerly when the module it imports from is already + # imported, so publish the target only once the statement has run. + self.enterContext(uncache('lazy_patch_user', 'lazy_patch_target')) + user = ModuleType('lazy_patch_user') + exec('lazy from lazy_patch_target import function', user.__dict__) + sys.modules['lazy_patch_user'] = user + target = ModuleType('lazy_patch_target') + exec('def function(arg): pass', target.__dict__) + sys.modules['lazy_patch_target'] = target + + self.assertIsInstance(user.__dict__['function'], LazyImportType) + return user + + def test_autospec(self): + module = self.lazy_module() + with patch.object(module, 'function', autospec=True) as mock_function: + mock_function('arg') + with self.assertRaises(TypeError): + mock_function('arg', 'extra') + + def test_spec(self): + module = self.lazy_module() + with patch.object(module, 'function', spec=True) as mock_function: + mock_function('arg') + + def test_spec_set(self): + module = self.lazy_module() + with patch.object(module, 'function', spec_set=True) as mock_function: + mock_function('arg') + + if __name__ == '__main__': unittest.main() diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py index 1effc70b5323139..d3d47ff81e85969 100644 --- a/Lib/unittest/mock.py +++ b/Lib/unittest/mock.py @@ -35,7 +35,7 @@ import threading from annotationlib import Format from dataclasses import fields, is_dataclass -from types import CodeType, ModuleType, MethodType +from types import CodeType, LazyImportType, ModuleType, MethodType from unittest.util import safe_repr from functools import wraps, partial from threading import RLock @@ -1507,6 +1507,10 @@ def __enter__(self): original, local = self.get_original() + if (isinstance(original, LazyImportType) + and (spec is True or spec_set is True or autospec is True)): + original = original.resolve() + if new is DEFAULT and autospec is None: inherit = False if spec is True: diff --git a/Misc/NEWS.d/next/Library/2026-09-17-21-05-42.gh-issue-153888.Ht7Yqb.rst b/Misc/NEWS.d/next/Library/2026-09-17-21-05-42.gh-issue-153888.Ht7Yqb.rst new file mode 100644 index 000000000000000..278c9560cec216e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-17-21-05-42.gh-issue-153888.Ht7Yqb.rst @@ -0,0 +1,2 @@ +Fix :func:`unittest.mock.patch` with ``autospec``, ``spec``, or ``spec_set`` +when used with lazy-imported objects.