求助:单文件正常但Python包中Mock urllib.request.urlopen失效
I've run into this exact issue before—mock works fine in a single file, but breaks when moving code into a package with imports in __init__.py. Let's break down why this happens and how to fix it.
The Root Cause
The core problem boils down to how unittest.mock.patch works: you have to patch the object in the namespace where it's being used, not where it's defined.
When you import fetch_data into your package's __init__.py, the function still references the urlopen variable from its original module (mypackage.module). If your test patches the wrong path (like a local relative path instead of the full package module path), the mock won't attach to the urlopen instance that fetch_data actually uses. This leads to the real urlopen being called, causing your "test is not a valid URL" error.
Reproducing the Scenario
First, let's confirm the package structure we're working with:
mypackage/ ├── __init__.py ├── module.py └── module_test.py
module.py
from urllib.request import urlopen def fetch_data(url): response = urlopen(url) return response.read()
init.py
from .module import fetch_data
The Broken Test (Your test_not_working)
This fails because the patch path is incorrect:
def test_not_working(self): # Wrong: using a relative path instead of the full package module path with patch('module.urlopen') as mock_urlopen: mock_response = Mock() mock_response.read.return_value = b'test data' mock_urlopen.return_value = mock_response result = fetch_data('http://example.com') # Uses real urlopen!
The Fix
Correct Test Code
Use the full package module path to patch the urlopen instance that fetch_data actually uses. Here's the working version:
import unittest from unittest.mock import patch, Mock from mypackage import fetch_data # Import from the package class TestFetchData(unittest.TestCase): def test_working_mock(self): # Correct: patch the urlopen in the module where fetch_data lives with patch("mypackage.module.urlopen") as mock_urlopen: # Set up the mock response mock_response = Mock() mock_response.read.return_value = b"Mocked response content" mock_urlopen.return_value = mock_response # Run the function under test result = fetch_data("http://example.com") # Verify everything worked mock_urlopen.assert_called_once_with("http://example.com") self.assertEqual(result, b"Mocked response content") def test_global_mock_alternative(self): # Optional: Patch the global urllib.request.urlopen (affects all uses) with patch("urllib.request.urlopen") as mock_urlopen: mock_response = Mock() mock_response.read.return_value = b"Global mock content" mock_urlopen.return_value = mock_response result = fetch_data("http://example.com") self.assertEqual(result, b"Global mock content") if __name__ == "__main__": unittest.main()
Why This Works
- When you import
fetch_datafrommypackage, it still executes code frommypackage.module. Theurlopenit uses is the one bound to themypackage.modulenamespace. - By patching
"mypackage.module.urlopen", we're replacing exactly that bound instance, sofetch_datauses our mock instead of the realurlopen.
Best Practices
- Always use full module paths for patches, especially in package structures. Relative paths lead to inconsistent behavior.
- Remember the patch rule: Patch where the object is used, not where it's defined. If a function in module A uses an object imported from module B, patch
A.object, notB.object(unless you want a global mock). - Avoid ambiguous imports: Be explicit about where your functions and objects come from to make patch paths easier to get right.
内容的提问来源于stack exchange,提问作者j-maas

